aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/commands/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/commands/base.py')
-rw-r--r--lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/commands/base.py618
1 files changed, 0 insertions, 618 deletions
diff --git a/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/commands/base.py b/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/commands/base.py
deleted file mode 100644
index b18d199c..00000000
--- a/lib/python2.7/site-packages/buildbot_slave-0.8.8-py2.7.egg/buildslave/commands/base.py
+++ /dev/null
@@ -1,618 +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 os
-from base64 import b64encode
-import sys
-import shutil
-
-from zope.interface import implements
-from twisted.internet import reactor, threads, defer
-from twisted.python import log, failure, runtime
-
-from buildslave.interfaces import ISlaveCommand
-from buildslave import runprocess
-from buildslave.exceptions import AbandonChain
-from buildslave.commands import utils
-from buildslave import util
-
-# this used to be a CVS $-style "Revision" auto-updated keyword, but since I
-# moved to Darcs as the primary repository, this is updated manually each
-# time this file is changed. The last cvs_ver that was here was 1.51 .
-command_version = "2.15"
-
-# version history:
-# >=1.17: commands are interruptable
-# >=1.28: Arch understands 'revision', added Bazaar
-# >=1.33: Source classes understand 'retry'
-# >=1.39: Source classes correctly handle changes in branch (except Git)
-# Darcs accepts 'revision' (now all do but Git) (well, and P4Sync)
-# Arch/Baz should accept 'build-config'
-# >=1.51: (release 0.7.3)
-# >= 2.1: SlaveShellCommand now accepts 'initial_stdin', 'keep_stdin_open',
-# and 'logfiles'. It now sends 'log' messages in addition to
-# stdout/stdin/header/rc. It acquired writeStdin/closeStdin methods,
-# but these are not remotely callable yet.
-# (not externally visible: ShellCommandPP has writeStdin/closeStdin.
-# ShellCommand accepts new arguments (logfiles=, initialStdin=,
-# keepStdinOpen=) and no longer accepts stdin=)
-# (release 0.7.4)
-# >= 2.2: added monotone, uploadFile, and downloadFile (release 0.7.5)
-# >= 2.3: added bzr (release 0.7.6)
-# >= 2.4: Git understands 'revision' and branches
-# >= 2.5: workaround added for remote 'hg clone --rev REV' when hg<0.9.2
-# >= 2.6: added uploadDirectory
-# >= 2.7: added usePTY option to SlaveShellCommand
-# >= 2.8: added username and password args to SVN class
-# >= 2.9: add depth arg to SVN class
-# >= 2.10: CVS can handle 'extra_options' and 'export_options'
-# >= 2.11: Arch, Bazaar, and Monotone removed
-# >= 2.12: SlaveShellCommand no longer accepts 'keep_stdin_open'
-# >= 2.13: SlaveFileUploadCommand supports option 'keepstamp'
-# >= 2.14: RemoveDirectory can delete multiple directories
-# >= 2.15: 'interruptSignal' option is added to SlaveShellCommand
-
-class Command:
- implements(ISlaveCommand)
-
- """This class defines one command that can be invoked by the build master.
- The command is executed on the slave side, and always sends back a
- completion message when it finishes. It may also send intermediate status
- as it runs (by calling builder.sendStatus). Some commands can be
- interrupted (either by the build master or a local timeout), in which
- case the step is expected to complete normally with a status message that
- indicates an error occurred.
-
- These commands are used by BuildSteps on the master side. Each kind of
- BuildStep uses a single Command. The slave must implement all the
- Commands required by the set of BuildSteps used for any given build:
- this is checked at startup time.
-
- All Commands are constructed with the same signature:
- c = CommandClass(builder, stepid, args)
- where 'builder' is the parent SlaveBuilder object, and 'args' is a
- dict that is interpreted per-command.
-
- The setup(args) method is available for setup, and is run from __init__.
-
- The Command is started with start(). This method must be implemented in a
- subclass, and it should return a Deferred. When your step is done, you
- should fire the Deferred (the results are not used). If the command is
- interrupted, it should fire the Deferred anyway.
-
- While the command runs. it may send status messages back to the
- buildmaster by calling self.sendStatus(statusdict). The statusdict is
- interpreted by the master-side BuildStep however it likes.
-
- A separate completion message is sent when the deferred fires, which
- indicates that the Command has finished, but does not carry any status
- data. If the Command needs to return an exit code of some sort, that
- should be sent as a regular status message before the deferred is fired .
- Once builder.commandComplete has been run, no more status messages may be
- sent.
-
- If interrupt() is called, the Command should attempt to shut down as
- quickly as possible. Child processes should be killed, new ones should
- not be started. The Command should send some kind of error status update,
- then complete as usual by firing the Deferred.
-
- .interrupted should be set by interrupt(), and can be tested to avoid
- sending multiple error status messages.
-
- If .running is False, the bot is shutting down (or has otherwise lost the
- connection to the master), and should not send any status messages. This
- is checked in Command.sendStatus .
-
- """
-
- # builder methods:
- # sendStatus(dict) (zero or more)
- # commandComplete() or commandInterrupted() (one, at end)
-
- debug = False
- interrupted = False
- running = False # set by Builder, cleared on shutdown or when the
- # Deferred fires
-
- _reactor = reactor
-
- def __init__(self, builder, stepId, args):
- self.builder = builder
- self.stepId = stepId # just for logging
- self.args = args
- self.startTime = None
- self.setup(args)
-
- def setup(self, args):
- """Override this in a subclass to extract items from the args dict."""
- pass
-
- def doStart(self):
- self.running = True
- self.startTime = util.now(self._reactor)
- d = defer.maybeDeferred(self.start)
- def commandComplete(res):
- self.sendStatus({"elapsed": util.now(self._reactor) - self.startTime})
- self.running = False
- return res
- d.addBoth(commandComplete)
- return d
-
- def start(self):
- """Start the command. This method should return a Deferred that will
- fire when the command has completed. The Deferred's argument will be
- ignored.
-
- This method should be overridden by subclasses."""
- raise NotImplementedError, "You must implement this in a subclass"
-
- def sendStatus(self, status):
- """Send a status update to the master."""
- if self.debug:
- log.msg("sendStatus", status)
- if not self.running:
- log.msg("would sendStatus but not .running")
- return
- self.builder.sendUpdate(status)
-
- def doInterrupt(self):
- self.running = False
- self.interrupt()
-
- def interrupt(self):
- """Override this in a subclass to allow commands to be interrupted.
- May be called multiple times, test and set self.interrupted=True if
- this matters."""
- pass
-
- # utility methods, mostly used by SlaveShellCommand and the like
-
- def _abandonOnFailure(self, rc):
- if type(rc) is not int:
- log.msg("weird, _abandonOnFailure was given rc=%s (%s)" % \
- (rc, type(rc)))
- assert isinstance(rc, int)
- if rc != 0:
- raise AbandonChain(rc)
- return rc
-
- def _sendRC(self, res):
- self.sendStatus({'rc': 0})
-
- def _checkAbandoned(self, why):
- log.msg("_checkAbandoned", why)
- why.trap(AbandonChain)
- log.msg(" abandoning chain", why.value)
- self.sendStatus({'rc': why.value.args[0]})
- return None
-
-class SourceBaseCommand(Command):
- """Abstract base class for Version Control System operations (checkout
- and update). This class extracts the following arguments from the
- dictionary received from the master:
-
- - ['workdir']: (required) the subdirectory where the buildable sources
- should be placed
-
- - ['mode']: one of update/copy/clobber/export, defaults to 'update'
-
- - ['revision']: (required) If not None, this is an int or string which indicates
- which sources (along a time-like axis) should be used.
- It is the thing you provide as the CVS -r or -D
- argument.
-
- - ['patch']: If not None, this is a tuple of (striplevel, patch)
- which contains a patch that should be applied after the
- checkout has occurred. Once applied, the tree is no
- longer eligible for use with mode='update', and it only
- makes sense to use this in conjunction with a
- ['revision'] argument. striplevel is an int, and patch
- is a string in standard unified diff format. The patch
- will be applied with 'patch -p%d <PATCH', with
- STRIPLEVEL substituted as %d. The command will fail if
- the patch process fails (rejected hunks).
-
- - ['timeout']: seconds of silence tolerated before we kill off the
- command
-
- - ['maxTime']: seconds before we kill off the command
-
- - ['retry']: If not None, this is a tuple of (delay, repeats)
- which means that any failed VC updates should be
- reattempted, up to REPEATS times, after a delay of
- DELAY seconds. This is intended to deal with slaves
- that experience transient network failures.
- """
-
- sourcedata = ""
-
- def setup(self, args):
- # if we need to parse the output, use this environment. Otherwise
- # command output will be in whatever the buildslave's native language
- # has been set to.
- self.env = os.environ.copy()
- self.env['LC_MESSAGES'] = "C"
- self.checkoutdir = args['srcdir']
- self.workdir = args['workdir']
- self.mode = args.get('mode', "update")
- self.revision = args.get('revision')
- self.patch = args.get('patch')
- self.timeout = args.get('timeout', 120)
- self.maxTime = args.get('maxTime', None)
- self.retry = args.get('retry')
- self.logEnviron = args.get('logEnviron',True)
- self._commandPaths = {}
- # VC-specific subclasses should override this to extract more args.
- # Make sure to upcall!
-
- def getCommand(self, name):
- """Wrapper around utils.getCommand that will output a resonable
- error message and raise AbandonChain if the command cannot be
- found"""
- if name not in self._commandPaths:
- try:
- self._commandPaths[name] = utils.getCommand(name)
- except RuntimeError:
- self.sendStatus({'stderr' : "could not find '%s'\n" % name})
- self.sendStatus({'stderr' : "PATH is '%s'\n" % os.environ.get('PATH', '')})
- raise AbandonChain(-1)
- return self._commandPaths[name]
-
- def start(self):
- self.sendStatus({'header': "starting " + self.header + "\n"})
- self.command = None
-
- # self.srcdir is where the VC system should put the sources
- if self.mode == "copy":
- self.srcdir = "source" # hardwired directory name, sorry
- else:
- self.srcdir = self.workdir
-
- self.sourcedatafile = os.path.join(self.builder.basedir,
- ".buildbot-sourcedata-" + b64encode(self.srcdir))
-
- # upgrade older versions to the new sourcedata location
- old_sd_path = os.path.join(self.builder.basedir, self.srcdir, ".buildbot-sourcedata")
- if os.path.exists(old_sd_path) and not os.path.exists(self.sourcedatafile):
- os.rename(old_sd_path, self.sourcedatafile)
-
- # also upgrade versions that didn't include the encoded version of the
- # source directory
- old_sd_path = os.path.join(self.builder.basedir, ".buildbot-sourcedata")
- if os.path.exists(old_sd_path) and not os.path.exists(self.sourcedatafile):
- os.rename(old_sd_path, self.sourcedatafile)
-
- d = defer.succeed(None)
- self.maybeClobber(d)
- if not (self.sourcedirIsUpdateable() and self.sourcedataMatches()):
- # the directory cannot be updated, so we have to clobber it.
- # Perhaps the master just changed modes from 'export' to
- # 'update'.
- d.addCallback(self.doClobber, self.srcdir)
-
- d.addCallback(self.doVC)
-
- if self.mode == "copy":
- d.addCallback(self.doCopy)
- if self.mode == "movecopy":
- d.addCallback(self.doCopy)
- if self.patch:
- d.addCallback(self.doPatch)
- d.addCallbacks(self._sendRC, self._checkAbandoned)
- return d
-
- def maybeClobber(self, d):
- # do we need to clobber anything?
- if self.mode in ("copy", "clobber", "export"):
- d.addCallback(self.doClobber, self.workdir)
-
- def interrupt(self):
- self.interrupted = True
- if self.command:
- self.command.kill("command interrupted")
-
- def doVC(self, res):
- if self.interrupted:
- raise AbandonChain(1)
- if self.sourcedirIsUpdateable() and self.sourcedataMatches():
- d = self.doVCUpdate()
- d.addBoth(self.maybeDoVCFallback)
- else:
- d = self.doVCFull()
- d.addBoth(self.maybeDoVCRetry)
- d.addCallback(self._abandonOnFailure)
- d.addCallback(self._handleGotRevision)
- d.addCallback(self.writeSourcedata)
- return d
-
- def sourcedataMatches(self):
- try:
- olddata = self.readSourcedata()
- if olddata != self.sourcedata:
- return False
- except IOError:
- return False
- return True
-
- def sourcedirIsPatched(self):
- return os.path.exists(os.path.join(self.builder.basedir,
- self.workdir,
- ".buildbot-patched"))
-
- def _handleGotRevision(self, res):
- d = defer.maybeDeferred(self.parseGotRevision)
- d.addCallback(lambda got_revision:
- self.sendStatus({'got_revision': got_revision}))
- return d
-
- def parseGotRevision(self):
- """Override this in a subclass. It should return a string that
- represents which revision was actually checked out, or a Deferred
- that will fire with such a string. If, in a future build, you were to
- pass this 'got_revision' string in as the 'revision' component of a
- SourceStamp, you should wind up with the same source code as this
- checkout just obtained.
-
- It is probably most useful to scan self.command.stdout for a string
- of some sort. Be sure to set keepStdout=True on the VC command that
- you run, so that you'll have something available to look at.
-
- If this information is unavailable, just return None."""
-
- return None
-
- def readSourcedata(self):
- """
- Read the sourcedata file and return its contents
-
- @returns: source data
- @raises: IOError if the file does not exist
- """
- return open(self.sourcedatafile, "r").read()
-
- def writeSourcedata(self, res):
- open(self.sourcedatafile, "w").write(self.sourcedata)
- return res
-
- def sourcedirIsUpdateable(self):
- """Returns True if the tree can be updated."""
- raise NotImplementedError("this must be implemented in a subclass")
-
- def doVCUpdate(self):
- """Returns a deferred with the steps to update a checkout."""
- raise NotImplementedError("this must be implemented in a subclass")
-
- def doVCFull(self):
- """Returns a deferred with the steps to do a fresh checkout."""
- raise NotImplementedError("this must be implemented in a subclass")
-
- def maybeDoVCFallback(self, rc):
- if type(rc) is int and rc == 0:
- return rc
- if self.interrupted:
- raise AbandonChain(1)
-
- # allow AssertionErrors to fall through, for benefit of the tests; for
- # all other errors, carry on to try the fallback
- if isinstance(rc, failure.Failure) and rc.check(AssertionError):
- return rc
-
- # Let VCS subclasses have an opportunity to handle
- # unrecoverable errors without having to clobber the repo
- self.maybeNotDoVCFallback(rc)
- msg = "update failed, clobbering and trying again"
- self.sendStatus({'header': msg + "\n"})
- log.msg(msg)
- d = self.doClobber(None, self.srcdir)
- d.addCallback(self.doVCFallback2)
- return d
-
- def doVCFallback2(self, res):
- msg = "now retrying VC operation"
- self.sendStatus({'header': msg + "\n"})
- log.msg(msg)
- d = self.doVCFull()
- d.addBoth(self.maybeDoVCRetry)
- d.addCallback(self._abandonOnFailure)
- return d
-
- def maybeNotDoVCFallback(self, rc):
- """Override this in a subclass if you want to detect unrecoverable
- checkout errors where clobbering the repo wouldn't help, and stop
- the current VC chain before it clobbers the repo for future builds.
-
- Use 'raise AbandonChain' to pass up a halt if you do detect such."""
- pass
-
- def maybeDoVCRetry(self, res):
- """We get here somewhere after a VC chain has finished. res could
- be::
-
- - 0: the operation was successful
- - nonzero: the operation failed. retry if possible
- - AbandonChain: the operation failed, someone else noticed. retry.
- - Failure: some other exception, re-raise
- """
-
- if isinstance(res, failure.Failure):
- if self.interrupted:
- return res # don't re-try interrupted builds
- res.trap(AbandonChain)
- else:
- if type(res) is int and res == 0:
- return res
- if self.interrupted:
- raise AbandonChain(1)
- # if we get here, we should retry, if possible
- if self.retry:
- delay, repeats = self.retry
- if repeats >= 0:
- self.retry = (delay, repeats-1)
- msg = ("update failed, trying %d more times after %d seconds"
- % (repeats, delay))
- self.sendStatus({'header': msg + "\n"})
- log.msg(msg)
- d = defer.Deferred()
- # we are going to do a full checkout, so a clobber is
- # required first
- self.doClobber(d, self.workdir)
- if self.srcdir:
- self.doClobber(d, self.srcdir)
- d.addCallback(lambda res: self.doVCFull())
- d.addBoth(self.maybeDoVCRetry)
- self._reactor.callLater(delay, d.callback, None)
- return d
- return res
-
- def doClobber(self, dummy, dirname, chmodDone=False):
- d = os.path.join(self.builder.basedir, dirname)
- if runtime.platformType != "posix":
- d = threads.deferToThread(utils.rmdirRecursive, d)
- def cb(_):
- return 0 # rc=0
- def eb(f):
- self.sendStatus({'header' : 'exception from rmdirRecursive\n' + f.getTraceback()})
- return -1 # rc=-1
- d.addCallbacks(cb, eb)
- return d
- command = ["rm", "-rf", d]
- c = runprocess.RunProcess(self.builder, command, self.builder.basedir,
- sendRC=0, timeout=self.timeout, maxTime=self.maxTime,
- logEnviron=self.logEnviron, usePTY=False)
-
- self.command = c
- # sendRC=0 means the rm command will send stdout/stderr to the
- # master, but not the rc=0 when it finishes. That job is left to
- # _sendRC
- d = c.start()
- # The rm -rf may fail if there is a left-over subdir with chmod 000
- # permissions. So if we get a failure, we attempt to chmod suitable
- # permissions and re-try the rm -rf.
- if chmodDone:
- d.addCallback(self._abandonOnFailure)
- else:
- d.addCallback(lambda rc: self.doClobberTryChmodIfFail(rc, dirname))
- return d
-
- def doClobberTryChmodIfFail(self, rc, dirname):
- assert isinstance(rc, int)
- if rc == 0:
- return defer.succeed(0)
- # Attempt a recursive chmod and re-try the rm -rf after.
-
- command = ["chmod", "-Rf", "u+rwx", os.path.join(self.builder.basedir, dirname)]
- if sys.platform.startswith('freebsd'):
- # Work around a broken 'chmod -R' on FreeBSD (it tries to recurse into a
- # directory for which it doesn't have permission, before changing that
- # permission) by running 'find' instead
- command = ["find", os.path.join(self.builder.basedir, dirname),
- '-exec', 'chmod', 'u+rwx', '{}', ';' ]
- c = runprocess.RunProcess(self.builder, command, self.builder.basedir,
- sendRC=0, timeout=self.timeout, maxTime=self.maxTime,
- logEnviron=self.logEnviron, usePTY=False)
-
- self.command = c
- d = c.start()
- d.addCallback(self._abandonOnFailure)
- d.addCallback(lambda dummy: self.doClobber(dummy, dirname, True))
- return d
-
- def doCopy(self, res):
- # now copy tree to workdir
- fromdir = os.path.join(self.builder.basedir, self.srcdir)
- todir = os.path.join(self.builder.basedir, self.workdir)
- if runtime.platformType != "posix":
- d = threads.deferToThread(shutil.copytree, fromdir, todir)
- def cb(_):
- return 0 # rc=0
- def eb(f):
- self.sendStatus({'header' : 'exception from copytree\n' + f.getTraceback()})
- return -1 # rc=-1
- d.addCallbacks(cb, eb)
- return d
-
- if not os.path.exists(os.path.dirname(todir)):
- os.makedirs(os.path.dirname(todir))
- if os.path.exists(todir):
- # I don't think this happens, but just in case..
- log.msg("cp target '%s' already exists -- cp will not do what you think!" % todir)
-
- command = ['cp', '-R', '-P', '-p', fromdir, todir]
- c = runprocess.RunProcess(self.builder, command, self.builder.basedir,
- sendRC=False, timeout=self.timeout, maxTime=self.maxTime,
- logEnviron=self.logEnviron, usePTY=False)
- self.command = c
- d = c.start()
- d.addCallback(self._abandonOnFailure)
- return d
-
- def doPatch(self, res):
- patchlevel = self.patch[0]
- diff = self.patch[1]
- root = None
- if len(self.patch) >= 3:
- root = self.patch[2]
- command = [
- utils.getCommand("patch"),
- '-p%d' % patchlevel,
- '--remove-empty-files',
- '--force',
- '--forward',
- '-i', '.buildbot-diff',
- ]
- dir = os.path.join(self.builder.basedir, self.workdir)
- # Mark the directory so we don't try to update it later, or at least try
- # to revert first.
- open(os.path.join(dir, ".buildbot-patched"), "w").write("patched\n")
-
- # write the diff to a file, for reading later
- open(os.path.join(dir, ".buildbot-diff"), "w").write(diff)
-
- # Update 'dir' with the 'root' option. Make sure it is a subdirectory
- # of dir.
- if (root and
- os.path.abspath(os.path.join(dir, root)
- ).startswith(os.path.abspath(dir))):
- dir = os.path.join(dir, root)
-
- # now apply the patch
- c = runprocess.RunProcess(self.builder, command, dir,
- sendRC=False, timeout=self.timeout,
- maxTime=self.maxTime, logEnviron=self.logEnviron,
- usePTY=False)
- self.command = c
- d = c.start()
-
- # clean up the temp file
- def cleanup(x):
- try:
- os.unlink(os.path.join(dir, ".buildbot-diff"))
- except:
- pass
- return x
- d.addBoth(cleanup)
-
- d.addCallback(self._abandonOnFailure)
- return d
-
- def setFileContents(self, filename, contents):
- """Put the given C{contents} in C{filename}; this is a bit more
- succinct than opening, writing, and closing, and has the advantage of
- being patchable in tests. Note that the enclosing directory is
- not automatically created, nor is this an "atomic" overwrite."""
- f = open(filename, 'w')
- f.write(contents)
- f.close()