aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildstep.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildstep.py')
-rw-r--r--lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildstep.py985
1 files changed, 0 insertions, 985 deletions
diff --git a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildstep.py b/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildstep.py
deleted file mode 100644
index c8272aa4..00000000
--- a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/process/buildstep.py
+++ /dev/null
@@ -1,985 +0,0 @@
-# This file is part of Buildbot. Buildbot is free software: you can
-# redistribute it and/or modify it under the terms of the GNU General Public
-# License as published by the Free Software Foundation, version 2.
-#
-# This program is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# this program; if not, write to the Free Software Foundation, Inc., 51
-# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Copyright Buildbot Team Members
-
-import re
-
-from zope.interface import implements
-from twisted.internet import defer, error
-from twisted.protocols import basic
-from twisted.spread import pb
-from twisted.python import log, components
-from twisted.python.failure import Failure
-from twisted.web.util import formatFailure
-from twisted.python.reflect import accumulateClassList
-
-from buildbot import interfaces, util, config
-from buildbot.status import progress
-from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED, \
- EXCEPTION, RETRY, worst_status
-from buildbot.process import metrics, properties
-from buildbot.util.eventual import eventually
-
-class BuildStepFailed(Exception):
- pass
-
-class RemoteCommand(pb.Referenceable):
-
- # class-level unique identifier generator for command ids
- _commandCounter = 0
-
- active = False
- rc = None
- debug = False
-
- def __init__(self, remote_command, args, ignore_updates=False,
- collectStdout=False, collectStderr=False, decodeRC={0:SUCCESS}):
- self.logs = {}
- self.delayedLogs = {}
- self._closeWhenFinished = {}
- self.collectStdout = collectStdout
- self.collectStderr = collectStderr
- self.stdout = ''
- self.stderr = ''
-
- self._startTime = None
- self._remoteElapsed = None
- self.remote_command = remote_command
- self.args = args
- self.ignore_updates = ignore_updates
- self.decodeRC = decodeRC
-
- def __repr__(self):
- return "<RemoteCommand '%s' at %d>" % (self.remote_command, id(self))
-
- def run(self, step, remote):
- self.active = True
- self.step = step
- self.remote = remote
-
- # generate a new command id
- cmd_id = RemoteCommand._commandCounter
- RemoteCommand._commandCounter += 1
- self.commandID = "%d" % cmd_id
-
- log.msg("%s: RemoteCommand.run [%s]" % (self, self.commandID))
- self.deferred = defer.Deferred()
-
- d = defer.maybeDeferred(self._start)
-
- # _finished is called with an error for unknown commands, errors
- # that occur while the command is starting (including OSErrors in
- # exec()), StaleBroker (when the connection was lost before we
- # started), and pb.PBConnectionLost (when the slave isn't responding
- # over this connection, perhaps it had a power failure, or NAT
- # weirdness). If this happens, self.deferred is fired right away.
- d.addErrback(self._finished)
-
- # Connections which are lost while the command is running are caught
- # when our parent Step calls our .lostRemote() method.
- return self.deferred
-
- def useLog(self, log, closeWhenFinished=False, logfileName=None):
- assert interfaces.ILogFile.providedBy(log)
- if not logfileName:
- logfileName = log.getName()
- assert logfileName not in self.logs
- assert logfileName not in self.delayedLogs
- self.logs[logfileName] = log
- self._closeWhenFinished[logfileName] = closeWhenFinished
-
- def useLogDelayed(self, logfileName, activateCallBack, closeWhenFinished=False):
- assert logfileName not in self.logs
- assert logfileName not in self.delayedLogs
- self.delayedLogs[logfileName] = (activateCallBack, closeWhenFinished)
-
- def _start(self):
- self.updates = {}
- self._startTime = util.now()
-
- # This method only initiates the remote command.
- # We will receive remote_update messages as the command runs.
- # We will get a single remote_complete when it finishes.
- # We should fire self.deferred when the command is done.
- d = self.remote.callRemote("startCommand", self, self.commandID,
- self.remote_command, self.args)
- return d
-
- def _finished(self, failure=None):
- self.active = False
- # call .remoteComplete. If it raises an exception, or returns the
- # Failure that we gave it, our self.deferred will be errbacked. If
- # it does not (either it ate the Failure or there the step finished
- # normally and it didn't raise a new exception), self.deferred will
- # be callbacked.
- d = defer.maybeDeferred(self.remoteComplete, failure)
- # arrange for the callback to get this RemoteCommand instance
- # instead of just None
- d.addCallback(lambda r: self)
- # this fires the original deferred we returned from .run(),
- # with self as the result, or a failure
- d.addBoth(self.deferred.callback)
-
- def interrupt(self, why):
- log.msg("RemoteCommand.interrupt", self, why)
- if not self.active:
- log.msg(" but this RemoteCommand is already inactive")
- return defer.succeed(None)
- if not self.remote:
- log.msg(" but our .remote went away")
- return defer.succeed(None)
- if isinstance(why, Failure) and why.check(error.ConnectionLost):
- log.msg("RemoteCommand.disconnect: lost slave")
- self.remote = None
- self._finished(why)
- return defer.succeed(None)
-
- # tell the remote command to halt. Returns a Deferred that will fire
- # when the interrupt command has been delivered.
-
- d = defer.maybeDeferred(self.remote.callRemote, "interruptCommand",
- self.commandID, str(why))
- # the slave may not have remote_interruptCommand
- d.addErrback(self._interruptFailed)
- return d
-
- def _interruptFailed(self, why):
- log.msg("RemoteCommand._interruptFailed", self)
- # TODO: forcibly stop the Command now, since we can't stop it
- # cleanly
- return None
-
- def remote_update(self, updates):
- """
- I am called by the slave's L{buildbot.slave.bot.SlaveBuilder} so
- I can receive updates from the running remote command.
-
- @type updates: list of [object, int]
- @param updates: list of updates from the remote command
- """
- self.buildslave.messageReceivedFromSlave()
- max_updatenum = 0
- for (update, num) in updates:
- #log.msg("update[%d]:" % num)
- try:
- if self.active and not self.ignore_updates:
- self.remoteUpdate(update)
- except:
- # log failure, terminate build, let slave retire the update
- self._finished(Failure())
- # TODO: what if multiple updates arrive? should
- # skip the rest but ack them all
- if num > max_updatenum:
- max_updatenum = num
- return max_updatenum
-
- def remote_complete(self, failure=None):
- """
- Called by the slave's L{buildbot.slave.bot.SlaveBuilder} to
- notify me the remote command has finished.
-
- @type failure: L{twisted.python.failure.Failure} or None
-
- @rtype: None
- """
- self.buildslave.messageReceivedFromSlave()
- # call the real remoteComplete a moment later, but first return an
- # acknowledgement so the slave can retire the completion message.
- if self.active:
- eventually(self._finished, failure)
- return None
-
- def addStdout(self, data):
- if 'stdio' in self.logs:
- self.logs['stdio'].addStdout(data)
- if self.collectStdout:
- self.stdout += data
-
- def addStderr(self, data):
- if 'stdio' in self.logs:
- self.logs['stdio'].addStderr(data)
- if self.collectStderr:
- self.stderr += data
-
- def addHeader(self, data):
- if 'stdio' in self.logs:
- self.logs['stdio'].addHeader(data)
-
- def addToLog(self, logname, data):
- # Activate delayed logs on first data.
- if logname in self.delayedLogs:
- (activateCallBack, closeWhenFinished) = self.delayedLogs[logname]
- del self.delayedLogs[logname]
- loog = activateCallBack(self)
- self.logs[logname] = loog
- self._closeWhenFinished[logname] = closeWhenFinished
-
- if logname in self.logs:
- self.logs[logname].addStdout(data)
- else:
- log.msg("%s.addToLog: no such log %s" % (self, logname))
-
- @metrics.countMethod('RemoteCommand.remoteUpdate()')
- def remoteUpdate(self, update):
- if self.debug:
- for k,v in update.items():
- log.msg("Update[%s]: %s" % (k,v))
- if update.has_key('stdout'):
- # 'stdout': data
- self.addStdout(update['stdout'])
- if update.has_key('stderr'):
- # 'stderr': data
- self.addStderr(update['stderr'])
- if update.has_key('header'):
- # 'header': data
- self.addHeader(update['header'])
- if update.has_key('log'):
- # 'log': (logname, data)
- logname, data = update['log']
- self.addToLog(logname, data)
- if update.has_key('rc'):
- rc = self.rc = update['rc']
- log.msg("%s rc=%s" % (self, rc))
- self.addHeader("program finished with exit code %d\n" % rc)
- if update.has_key('elapsed'):
- self._remoteElapsed = update['elapsed']
-
- # TODO: these should be handled at the RemoteCommand level
- for k in update:
- if k not in ('stdout', 'stderr', 'header', 'rc'):
- if k not in self.updates:
- self.updates[k] = []
- self.updates[k].append(update[k])
-
- def remoteComplete(self, maybeFailure):
- if self._startTime and self._remoteElapsed:
- delta = (util.now() - self._startTime) - self._remoteElapsed
- metrics.MetricTimeEvent.log("RemoteCommand.overhead", delta)
-
- for name,loog in self.logs.items():
- if self._closeWhenFinished[name]:
- if maybeFailure:
- loog.addHeader("\nremoteFailed: %s" % maybeFailure)
- else:
- log.msg("closing log %s" % loog)
- loog.finish()
- return maybeFailure
-
- def results(self):
- if self.rc in self.decodeRC:
- return self.decodeRC[self.rc]
- return FAILURE
-
- def didFail(self):
- return self.results() == FAILURE
-LoggedRemoteCommand = RemoteCommand
-
-
-class LogObserver:
- implements(interfaces.ILogObserver)
-
- def setStep(self, step):
- self.step = step
-
- def setLog(self, loog):
- assert interfaces.IStatusLog.providedBy(loog)
- loog.subscribe(self, True)
-
- def logChunk(self, build, step, log, channel, text):
- if channel == interfaces.LOG_CHANNEL_STDOUT:
- self.outReceived(text)
- elif channel == interfaces.LOG_CHANNEL_STDERR:
- self.errReceived(text)
-
- # TODO: add a logEnded method? er, stepFinished?
-
- def outReceived(self, data):
- """This will be called with chunks of stdout data. Override this in
- your observer."""
- pass
-
- def errReceived(self, data):
- """This will be called with chunks of stderr data. Override this in
- your observer."""
- pass
-
-
-class LogLineObserver(LogObserver):
- def __init__(self):
- self.stdoutParser = basic.LineOnlyReceiver()
- self.stdoutParser.delimiter = "\n"
- self.stdoutParser.lineReceived = self.outLineReceived
- self.stdoutParser.transport = self # for the .disconnecting attribute
- self.disconnecting = False
-
- self.stderrParser = basic.LineOnlyReceiver()
- self.stderrParser.delimiter = "\n"
- self.stderrParser.lineReceived = self.errLineReceived
- self.stderrParser.transport = self
-
- def setMaxLineLength(self, max_length):
- """
- Set the maximum line length: lines longer than max_length are
- dropped. Default is 16384 bytes. Use sys.maxint for effective
- infinity.
- """
- self.stdoutParser.MAX_LENGTH = max_length
- self.stderrParser.MAX_LENGTH = max_length
-
- def outReceived(self, data):
- self.stdoutParser.dataReceived(data)
-
- def errReceived(self, data):
- self.stderrParser.dataReceived(data)
-
- def outLineReceived(self, line):
- """This will be called with complete stdout lines (not including the
- delimiter). Override this in your observer."""
- pass
-
- def errLineReceived(self, line):
- """This will be called with complete lines of stderr (not including
- the delimiter). Override this in your observer."""
- pass
-
-
-class RemoteShellCommand(RemoteCommand):
- def __init__(self, workdir, command, env=None,
- want_stdout=1, want_stderr=1,
- timeout=20*60, maxTime=None, logfiles={},
- usePTY="slave-config", logEnviron=True,
- collectStdout=False,collectStderr=False,
- interruptSignal=None,
- initialStdin=None, decodeRC={0:SUCCESS}):
-
- self.command = command # stash .command, set it later
- if env is not None:
- # avoid mutating the original master.cfg dictionary. Each
- # ShellCommand gets its own copy, any start() methods won't be
- # able to modify the original.
- env = env.copy()
- args = {'workdir': workdir,
- 'env': env,
- 'want_stdout': want_stdout,
- 'want_stderr': want_stderr,
- 'logfiles': logfiles,
- 'timeout': timeout,
- 'maxTime': maxTime,
- 'usePTY': usePTY,
- 'logEnviron': logEnviron,
- 'initial_stdin': initialStdin
- }
- if interruptSignal is not None:
- args['interruptSignal'] = interruptSignal
- RemoteCommand.__init__(self, "shell", args, collectStdout=collectStdout,
- collectStderr=collectStderr,
- decodeRC=decodeRC)
-
- def _start(self):
- self.args['command'] = self.command
- if self.remote_command == "shell":
- # non-ShellCommand slavecommands are responsible for doing this
- # fixup themselves
- if self.step.slaveVersion("shell", "old") == "old":
- self.args['dir'] = self.args['workdir']
- what = "command '%s' in dir '%s'" % (self.args['command'],
- self.args['workdir'])
- log.msg(what)
- return RemoteCommand._start(self)
-
- def __repr__(self):
- return "<RemoteShellCommand '%s'>" % repr(self.command)
-
-class _BuildStepFactory(util.ComparableMixin):
- """
- This is a wrapper to record the arguments passed to as BuildStep subclass.
- We use an instance of this class, rather than a closure mostly to make it
- easier to test that the right factories are getting created.
- """
- compare_attrs = ['factory', 'args', 'kwargs' ]
- implements(interfaces.IBuildStepFactory)
-
- def __init__(self, factory, *args, **kwargs):
- self.factory = factory
- self.args = args
- self.kwargs = kwargs
-
- def buildStep(self):
- try:
- return self.factory(*self.args, **self.kwargs)
- except:
- log.msg("error while creating step, factory=%s, args=%s, kwargs=%s"
- % (self.factory, self.args, self.kwargs))
- raise
-
-class BuildStep(object, properties.PropertiesMixin):
-
- haltOnFailure = False
- flunkOnWarnings = False
- flunkOnFailure = False
- warnOnWarnings = False
- warnOnFailure = False
- alwaysRun = False
- doStepIf = True
- hideStepIf = False
-
- # properties set on a build step are, by nature, always runtime properties
- set_runtime_properties = True
-
- # 'parms' holds a list of all the parameters we care about, to allow
- # users to instantiate a subclass of BuildStep with a mixture of
- # arguments, some of which are for us, some of which are for the subclass
- # (or a delegate of the subclass, like how ShellCommand delivers many
- # arguments to the RemoteShellCommand that it creates). Such delegating
- # subclasses will use this list to figure out which arguments are meant
- # for us and which should be given to someone else.
- parms = ['name', 'locks',
- 'haltOnFailure',
- 'flunkOnWarnings',
- 'flunkOnFailure',
- 'warnOnWarnings',
- 'warnOnFailure',
- 'alwaysRun',
- 'progressMetrics',
- 'useProgress',
- 'doStepIf',
- 'hideStepIf',
- ]
-
- name = "generic"
- locks = []
- progressMetrics = () # 'time' is implicit
- useProgress = True # set to False if step is really unpredictable
- build = None
- step_status = None
- progress = None
-
- def __init__(self, **kwargs):
- for p in self.__class__.parms:
- if kwargs.has_key(p):
- setattr(self, p, kwargs[p])
- del kwargs[p]
- if kwargs:
- config.error("%s.__init__ got unexpected keyword argument(s) %s" \
- % (self.__class__, kwargs.keys()))
- self._pendingLogObservers = []
-
- if not isinstance(self.name, str):
- config.error("BuildStep name must be a string: %r" % (self.name,))
-
- self._acquiringLock = None
- self.stopped = False
-
- def __new__(klass, *args, **kwargs):
- self = object.__new__(klass)
- self._factory = _BuildStepFactory(klass, *args, **kwargs)
- return self
-
- def describe(self, done=False):
- return [self.name]
-
- def setBuild(self, build):
- self.build = build
-
- def setBuildSlave(self, buildslave):
- self.buildslave = buildslave
-
- def setDefaultWorkdir(self, workdir):
- pass
-
- def addFactoryArguments(self, **kwargs):
- # this is here for backwards compatability
- pass
-
- def _getStepFactory(self):
- return self._factory
-
- def setStepStatus(self, step_status):
- self.step_status = step_status
-
- def setupProgress(self):
- if self.useProgress:
- sp = progress.StepProgress(self.name, self.progressMetrics)
- self.progress = sp
- self.step_status.setProgress(sp)
- return sp
- return None
-
- def setProgress(self, metric, value):
- if self.progress:
- self.progress.setProgress(metric, value)
-
- def startStep(self, remote):
- self.remote = remote
- self.deferred = defer.Deferred()
- # convert all locks into their real form
- self.locks = [(self.build.builder.botmaster.getLockByID(access.lockid), access)
- for access in self.locks ]
- # then narrow SlaveLocks down to the slave that this build is being
- # run on
- self.locks = [(l.getLock(self.build.slavebuilder.slave), la)
- for l, la in self.locks ]
-
- for l, la in self.locks:
- if l in self.build.locks:
- log.msg("Hey, lock %s is claimed by both a Step (%s) and the"
- " parent Build (%s)" % (l, self, self.build))
- raise RuntimeError("lock claimed by both Step and Build")
-
- # Set the step's text here so that the stepStarted notification sees
- # the correct description
- self.step_status.setText(self.describe(False))
- self.step_status.stepStarted()
-
- d = self.acquireLocks()
- d.addCallback(self._startStep_2)
- d.addErrback(self.failed)
- return self.deferred
-
- def acquireLocks(self, res=None):
- self._acquiringLock = None
- if not self.locks:
- return defer.succeed(None)
- if self.stopped:
- return defer.succeed(None)
- log.msg("acquireLocks(step %s, locks %s)" % (self, self.locks))
- for lock, access in self.locks:
- if not lock.isAvailable(self, access):
- self.step_status.setWaitingForLocks(True)
- log.msg("step %s waiting for lock %s" % (self, lock))
- d = lock.waitUntilMaybeAvailable(self, access)
- d.addCallback(self.acquireLocks)
- self._acquiringLock = (lock, access, d)
- return d
- # all locks are available, claim them all
- for lock, access in self.locks:
- lock.claim(self, access)
- self.step_status.setWaitingForLocks(False)
- return defer.succeed(None)
-
- def _startStep_2(self, res):
- if self.stopped:
- self.finished(EXCEPTION)
- return
-
- if self.progress:
- self.progress.start()
-
- if isinstance(self.doStepIf, bool):
- doStep = defer.succeed(self.doStepIf)
- else:
- doStep = defer.maybeDeferred(self.doStepIf, self)
-
- renderables = []
- accumulateClassList(self.__class__, 'renderables', renderables)
-
- def setRenderable(res, attr):
- setattr(self, attr, res)
-
- dl = [ doStep ]
- for renderable in renderables:
- d = self.build.render(getattr(self, renderable))
- d.addCallback(setRenderable, renderable)
- dl.append(d)
- dl = defer.gatherResults(dl)
-
- dl.addCallback(self._startStep_3)
- return dl
-
- @defer.inlineCallbacks
- def _startStep_3(self, doStep):
- doStep = doStep[0]
- try:
- if doStep:
- result = yield defer.maybeDeferred(self.start)
- if result == SKIPPED:
- doStep = False
- except:
- log.msg("BuildStep.startStep exception in .start")
- self.failed(Failure())
-
- if not doStep:
- self.step_status.setText(self.describe(True) + ['skipped'])
- self.step_status.setSkipped(True)
- # this return value from self.start is a shortcut to finishing
- # the step immediately; we skip calling finished() as
- # subclasses may have overridden that an expect it to be called
- # after start() (bug #837)
- eventually(self._finishFinished, SKIPPED)
-
- def start(self):
- raise NotImplementedError("your subclass must implement this method")
-
- def interrupt(self, reason):
- self.stopped = True
- if self._acquiringLock:
- lock, access, d = self._acquiringLock
- lock.stopWaitingUntilAvailable(self, access, d)
- d.callback(None)
-
- def releaseLocks(self):
- log.msg("releaseLocks(%s): %s" % (self, self.locks))
- for lock, access in self.locks:
- if lock.isOwner(self, access):
- lock.release(self, access)
- else:
- # This should only happen if we've been interrupted
- assert self.stopped
-
- def finished(self, results):
- if self.stopped and results != RETRY:
- # We handle this specially because we don't care about
- # the return code of an interrupted command; we know
- # that this should just be exception due to interrupt
- # At the same time we must respect RETRY status because it's used
- # to retry interrupted build due to some other issues for example
- # due to slave lost
- results = EXCEPTION
- self.step_status.setText(self.describe(True) +
- ["interrupted"])
- self.step_status.setText2(["interrupted"])
- self._finishFinished(results)
-
- def _finishFinished(self, results):
- # internal function to indicate that this step is done; this is separated
- # from finished() so that subclasses can override finished()
- if self.progress:
- self.progress.finish()
-
- try:
- hidden = self._maybeEvaluate(self.hideStepIf, results, self)
- except Exception:
- why = Failure()
- self.addHTMLLog("err.html", formatFailure(why))
- self.addCompleteLog("err.text", why.getTraceback())
- results = EXCEPTION
- hidden = False
-
- self.step_status.stepFinished(results)
- self.step_status.setHidden(hidden)
-
- self.releaseLocks()
- self.deferred.callback(results)
-
- def failed(self, why):
- # This can either be a BuildStepFailed exception/failure, meaning we
- # should call self.finished, or it can be a real exception, which should
- # be recorded as such.
- if why.check(BuildStepFailed):
- self.finished(FAILURE)
- return
-
- log.err(why, "BuildStep.failed; traceback follows")
- try:
- if self.progress:
- self.progress.finish()
- try:
- self.addCompleteLog("err.text", why.getTraceback())
- self.addHTMLLog("err.html", formatFailure(why))
- except Exception:
- log.err(Failure(), "error while formatting exceptions")
-
- # could use why.getDetailedTraceback() for more information
- self.step_status.setText([self.name, "exception"])
- self.step_status.setText2([self.name])
- self.step_status.stepFinished(EXCEPTION)
-
- hidden = self._maybeEvaluate(self.hideStepIf, EXCEPTION, self)
- self.step_status.setHidden(hidden)
- except Exception:
- log.err(Failure(), "exception during failure processing")
- # the progress stuff may still be whacked (the StepStatus may
- # think that it is still running), but the build overall will now
- # finish
-
- try:
- self.releaseLocks()
- except Exception:
- log.err(Failure(), "exception while releasing locks")
-
- log.msg("BuildStep.failed now firing callback")
- self.deferred.callback(EXCEPTION)
-
- # utility methods that BuildSteps may find useful
-
- def slaveVersion(self, command, oldversion=None):
- return self.build.getSlaveCommandVersion(command, oldversion)
-
- def slaveVersionIsOlderThan(self, command, minversion):
- sv = self.build.getSlaveCommandVersion(command, None)
- if sv is None:
- return True
- if map(int, sv.split(".")) < map(int, minversion.split(".")):
- return True
- return False
-
- def getSlaveName(self):
- return self.build.getSlaveName()
-
- def addLog(self, name):
- loog = self.step_status.addLog(name)
- self._connectPendingLogObservers()
- return loog
-
- def getLog(self, name):
- for l in self.step_status.getLogs():
- if l.getName() == name:
- return l
- raise KeyError("no log named '%s'" % (name,))
-
- def addCompleteLog(self, name, text):
- log.msg("addCompleteLog(%s)" % name)
- loog = self.step_status.addLog(name)
- size = loog.chunkSize
- for start in range(0, len(text), size):
- loog.addStdout(text[start:start+size])
- loog.finish()
- self._connectPendingLogObservers()
-
- def addHTMLLog(self, name, html):
- log.msg("addHTMLLog(%s)" % name)
- self.step_status.addHTMLLog(name, html)
- self._connectPendingLogObservers()
-
- def addLogObserver(self, logname, observer):
- assert interfaces.ILogObserver.providedBy(observer)
- observer.setStep(self)
- self._pendingLogObservers.append((logname, observer))
- self._connectPendingLogObservers()
-
- def _connectPendingLogObservers(self):
- if not self._pendingLogObservers:
- return
- if not self.step_status:
- return
- current_logs = {}
- for loog in self.step_status.getLogs():
- current_logs[loog.getName()] = loog
- for logname, observer in self._pendingLogObservers[:]:
- if logname in current_logs:
- observer.setLog(current_logs[logname])
- self._pendingLogObservers.remove((logname, observer))
-
- def addURL(self, name, url):
- self.step_status.addURL(name, url)
-
- def runCommand(self, c):
- c.buildslave = self.buildslave
- d = c.run(self, self.remote)
- return d
-
- @staticmethod
- def _maybeEvaluate(value, *args, **kwargs):
- if callable(value):
- value = value(*args, **kwargs)
- return value
-
-components.registerAdapter(
- BuildStep._getStepFactory,
- BuildStep, interfaces.IBuildStepFactory)
-components.registerAdapter(
- lambda step : interfaces.IProperties(step.build),
- BuildStep, interfaces.IProperties)
-
-
-class OutputProgressObserver(LogObserver):
- length = 0
-
- def __init__(self, name):
- self.name = name
-
- def logChunk(self, build, step, log, channel, text):
- self.length += len(text)
- self.step.setProgress(self.name, self.length)
-
-class LoggingBuildStep(BuildStep):
-
- progressMetrics = ('output',)
- logfiles = {}
-
- parms = BuildStep.parms + ['logfiles', 'lazylogfiles', 'log_eval_func']
- cmd = None
-
- renderables = [ 'logfiles', 'lazylogfiles' ]
-
- def __init__(self, logfiles={}, lazylogfiles=False, log_eval_func=None,
- *args, **kwargs):
- BuildStep.__init__(self, *args, **kwargs)
-
- if logfiles and not isinstance(logfiles, dict):
- config.error(
- "the ShellCommand 'logfiles' parameter must be a dictionary")
-
- # merge a class-level 'logfiles' attribute with one passed in as an
- # argument
- self.logfiles = self.logfiles.copy()
- self.logfiles.update(logfiles)
- self.lazylogfiles = lazylogfiles
- if log_eval_func and not callable(log_eval_func):
- config.error(
- "the 'log_eval_func' paramater must be a callable")
- self.log_eval_func = log_eval_func
- self.addLogObserver('stdio', OutputProgressObserver("output"))
-
- def addLogFile(self, logname, filename):
- self.logfiles[logname] = filename
-
- def buildCommandKwargs(self):
- kwargs = dict()
- kwargs['logfiles'] = self.logfiles
- return kwargs
-
- def startCommand(self, cmd, errorMessages=[]):
- """
- @param cmd: a suitable RemoteCommand which will be launched, with
- all output being put into our self.stdio_log LogFile
- """
- log.msg("ShellCommand.startCommand(cmd=%s)" % (cmd,))
- log.msg(" cmd.args = %r" % (cmd.args))
- self.cmd = cmd # so we can interrupt it
- self.step_status.setText(self.describe(False))
-
- # stdio is the first log
- self.stdio_log = stdio_log = self.addLog("stdio")
- cmd.useLog(stdio_log, True)
- for em in errorMessages:
- stdio_log.addHeader(em)
- # TODO: consider setting up self.stdio_log earlier, and have the
- # code that passes in errorMessages instead call
- # self.stdio_log.addHeader() directly.
-
- # there might be other logs
- self.setupLogfiles(cmd, self.logfiles)
-
- d = self.runCommand(cmd) # might raise ConnectionLost
- d.addCallback(lambda res: self.commandComplete(cmd))
- d.addCallback(lambda res: self.createSummary(cmd.logs['stdio']))
- d.addCallback(lambda res: self.evaluateCommand(cmd)) # returns results
- def _gotResults(results):
- self.setStatus(cmd, results)
- return results
- d.addCallback(_gotResults) # returns results
- d.addCallbacks(self.finished, self.checkDisconnect)
- d.addErrback(self.failed)
-
- def setupLogfiles(self, cmd, logfiles):
- for logname,remotefilename in logfiles.items():
- if self.lazylogfiles:
- # Ask RemoteCommand to watch a logfile, but only add
- # it when/if we see any data.
- #
- # The dummy default argument local_logname is a work-around for
- # Python name binding; default values are bound by value, but
- # captured variables in the body are bound by name.
- callback = lambda cmd_arg, local_logname=logname: self.addLog(local_logname)
- cmd.useLogDelayed(logname, callback, True)
- else:
- # tell the BuildStepStatus to add a LogFile
- newlog = self.addLog(logname)
- # and tell the RemoteCommand to feed it
- cmd.useLog(newlog, True)
-
- def interrupt(self, reason):
- # TODO: consider adding an INTERRUPTED or STOPPED status to use
- # instead of FAILURE, might make the text a bit more clear.
- # 'reason' can be a Failure, or text
- BuildStep.interrupt(self, reason)
- if self.step_status.isWaitingForLocks():
- self.addCompleteLog('interrupt while waiting for locks', str(reason))
- else:
- self.addCompleteLog('interrupt', str(reason))
-
- if self.cmd:
- d = self.cmd.interrupt(reason)
- d.addErrback(log.err, 'while interrupting command')
-
- def checkDisconnect(self, f):
- f.trap(error.ConnectionLost)
- self.step_status.setText(self.describe(True) +
- ["exception", "slave", "lost"])
- self.step_status.setText2(["exception", "slave", "lost"])
- return self.finished(RETRY)
-
- def commandComplete(self, cmd):
- pass
-
- def createSummary(self, stdio):
- pass
-
- def evaluateCommand(self, cmd):
- if self.log_eval_func:
- return self.log_eval_func(cmd, self.step_status)
- return cmd.results()
-
- def getText(self, cmd, results):
- if results == SUCCESS:
- return self.describe(True)
- elif results == WARNINGS:
- return self.describe(True) + ["warnings"]
- elif results == EXCEPTION:
- return self.describe(True) + ["exception"]
- else:
- return self.describe(True) + ["failed"]
-
- def getText2(self, cmd, results):
- return [self.name]
-
- def maybeGetText2(self, cmd, results):
- if results == SUCCESS:
- # successful steps do not add anything to the build's text
- pass
- elif results == WARNINGS:
- if (self.flunkOnWarnings or self.warnOnWarnings):
- # we're affecting the overall build, so tell them why
- return self.getText2(cmd, results)
- else:
- if (self.haltOnFailure or self.flunkOnFailure
- or self.warnOnFailure):
- # we're affecting the overall build, so tell them why
- return self.getText2(cmd, results)
- return []
-
- def setStatus(self, cmd, results):
- # this is good enough for most steps, but it can be overridden to
- # get more control over the displayed text
- self.step_status.setText(self.getText(cmd, results))
- self.step_status.setText2(self.maybeGetText2(cmd, results))
-
-
-# Parses the logs for a list of regexs. Meant to be invoked like:
-# regexes = ((re.compile(...), FAILURE), (re.compile(...), WARNINGS))
-# self.addStep(ShellCommand,
-# command=...,
-# ...,
-# log_eval_func=lambda c,s: regex_log_evaluator(c, s, regexs)
-# )
-def regex_log_evaluator(cmd, step_status, regexes):
- worst = cmd.results()
- for err, possible_status in regexes:
- # worst_status returns the worse of the two status' passed to it.
- # we won't be changing "worst" unless possible_status is worse than it,
- # so we don't even need to check the log if that's the case
- if worst_status(worst, possible_status) == possible_status:
- if isinstance(err, (basestring)):
- err = re.compile(".*%s.*" % err, re.DOTALL)
- for l in cmd.logs.values():
- if err.search(l.getText()):
- worst = possible_status
- return worst
-
-# (WithProperties used to be available in this module)
-from buildbot.process.properties import WithProperties
-_hush_pyflakes = [WithProperties]
-del _hush_pyflakes
-