diff options
Diffstat (limited to 'lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/builder.py')
-rw-r--r-- | lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/builder.py | 583 |
1 files changed, 0 insertions, 583 deletions
diff --git a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/builder.py b/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/builder.py deleted file mode 100644 index 8430d13e..00000000 --- a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/builder.py +++ /dev/null @@ -1,583 +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 - -from __future__ import with_statement - - -import os, re, itertools -from cPickle import load, dump - -from zope.interface import implements -from twisted.python import log, runtime -from twisted.persisted import styles -from buildbot import interfaces, util -from buildbot.util.lru import LRUCache -from buildbot.status.event import Event -from buildbot.status.build import BuildStatus -from buildbot.status.buildrequest import BuildRequestStatus - -# user modules expect these symbols to be present here -from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, SKIPPED -from buildbot.status.results import EXCEPTION, RETRY, Results, worst_status -_hush_pyflakes = [ SUCCESS, WARNINGS, FAILURE, SKIPPED, - EXCEPTION, RETRY, Results, worst_status ] - -class BuilderStatus(styles.Versioned): - """I handle status information for a single process.build.Builder object. - That object sends status changes to me (frequently as Events), and I - provide them on demand to the various status recipients, like the HTML - waterfall display and the live status clients. It also sends build - summaries to me, which I log and provide to status clients who aren't - interested in seeing details of the individual build steps. - - I am responsible for maintaining the list of historic Events and Builds, - pruning old ones, and loading them from / saving them to disk. - - I live in the buildbot.process.build.Builder object, in the - .builder_status attribute. - - @type category: string - @ivar category: user-defined category this builder belongs to; can be - used to filter on in status clients - """ - - implements(interfaces.IBuilderStatus, interfaces.IEventSource) - - persistenceVersion = 1 - persistenceForgets = ( 'wasUpgraded', ) - - category = None - currentBigState = "offline" # or idle/waiting/interlocked/building - basedir = None # filled in by our parent - - def __init__(self, buildername, category, master, description): - self.name = buildername - self.category = category - self.description = description - self.master = master - - self.slavenames = [] - self.events = [] - # these three hold Events, and are used to retrieve the current - # state of the boxes. - self.lastBuildStatus = None - #self.currentBig = None - #self.currentSmall = None - self.currentBuilds = [] - self.nextBuild = None - self.watchers = [] - self.buildCache = LRUCache(self.cacheMiss) - - # persistence - - def __getstate__(self): - # when saving, don't record transient stuff like what builds are - # currently running, because they won't be there when we start back - # up. Nor do we save self.watchers, nor anything that gets set by our - # parent like .basedir and .status - d = styles.Versioned.__getstate__(self) - d['watchers'] = [] - del d['buildCache'] - for b in self.currentBuilds: - b.saveYourself() - # TODO: push a 'hey, build was interrupted' event - del d['currentBuilds'] - d.pop('pendingBuilds', None) - del d['currentBigState'] - del d['basedir'] - del d['status'] - del d['nextBuildNumber'] - del d['master'] - return d - - def __setstate__(self, d): - # when loading, re-initialize the transient stuff. Remember that - # upgradeToVersion1 and such will be called after this finishes. - styles.Versioned.__setstate__(self, d) - self.buildCache = LRUCache(self.cacheMiss) - self.currentBuilds = [] - self.watchers = [] - self.slavenames = [] - # self.basedir must be filled in by our parent - # self.status must be filled in by our parent - # self.master must be filled in by our parent - - def upgradeToVersion1(self): - if hasattr(self, 'slavename'): - self.slavenames = [self.slavename] - del self.slavename - if hasattr(self, 'nextBuildNumber'): - del self.nextBuildNumber # determineNextBuildNumber chooses this - self.wasUpgraded = True - - def determineNextBuildNumber(self): - """Scan our directory of saved BuildStatus instances to determine - what our self.nextBuildNumber should be. Set it one larger than the - highest-numbered build we discover. This is called by the top-level - Status object shortly after we are created or loaded from disk. - """ - existing_builds = [int(f) - for f in os.listdir(self.basedir) - if re.match("^\d+$", f)] - if existing_builds: - self.nextBuildNumber = max(existing_builds) + 1 - else: - self.nextBuildNumber = 0 - - def saveYourself(self): - for b in self.currentBuilds: - if not b.isFinished: - # interrupted build, need to save it anyway. - # BuildStatus.saveYourself will mark it as interrupted. - b.saveYourself() - filename = os.path.join(self.basedir, "builder") - tmpfilename = filename + ".tmp" - try: - with open(tmpfilename, "wb") as f: - dump(self, f, -1) - if runtime.platformType == 'win32': - # windows cannot rename a file on top of an existing one - if os.path.exists(filename): - os.unlink(filename) - os.rename(tmpfilename, filename) - except: - log.msg("unable to save builder %s" % self.name) - log.err() - - # build cache management - - def setCacheSize(self, size): - self.buildCache.set_max_size(size) - - def makeBuildFilename(self, number): - return os.path.join(self.basedir, "%d" % number) - - def getBuildByNumber(self, number): - return self.buildCache.get(number) - - def loadBuildFromFile(self, number): - filename = self.makeBuildFilename(number) - try: - log.msg("Loading builder %s's build %d from on-disk pickle" - % (self.name, number)) - with open(filename, "rb") as f: - build = load(f) - build.setProcessObjects(self, self.master) - - # (bug #1068) if we need to upgrade, we probably need to rewrite - # this pickle, too. We determine this by looking at the list of - # Versioned objects that have been unpickled, and (after doUpgrade) - # checking to see if any of them set wasUpgraded. The Versioneds' - # upgradeToVersionNN methods all set this. - versioneds = styles.versionedsToUpgrade - styles.doUpgrade() - if True in [ hasattr(o, 'wasUpgraded') for o in versioneds.values() ]: - log.msg("re-writing upgraded build pickle") - build.saveYourself() - - # check that logfiles exist - build.checkLogfiles() - return build - except IOError: - raise IndexError("no such build %d" % number) - except EOFError: - raise IndexError("corrupted build pickle %d" % number) - - def cacheMiss(self, number, **kwargs): - # If kwargs['val'] exists, this is a new value being added to - # the cache. Just return it. - if 'val' in kwargs: - return kwargs['val'] - - # first look in currentBuilds - for b in self.currentBuilds: - if b.number == number: - return b - - # then fall back to loading it from disk - return self.loadBuildFromFile(number) - - def prune(self, events_only=False): - # begin by pruning our own events - eventHorizon = self.master.config.eventHorizon - self.events = self.events[-eventHorizon:] - - if events_only: - return - - # get the horizons straight - buildHorizon = self.master.config.buildHorizon - if buildHorizon is not None: - earliest_build = self.nextBuildNumber - buildHorizon - else: - earliest_build = 0 - - logHorizon = self.master.config.logHorizon - if logHorizon is not None: - earliest_log = self.nextBuildNumber - logHorizon - else: - earliest_log = 0 - - if earliest_log < earliest_build: - earliest_log = earliest_build - - if earliest_build == 0: - return - - # skim the directory and delete anything that shouldn't be there anymore - build_re = re.compile(r"^([0-9]+)$") - build_log_re = re.compile(r"^([0-9]+)-.*$") - # if the directory doesn't exist, bail out here - if not os.path.exists(self.basedir): - return - - for filename in os.listdir(self.basedir): - num = None - mo = build_re.match(filename) - is_logfile = False - if mo: - num = int(mo.group(1)) - else: - mo = build_log_re.match(filename) - if mo: - num = int(mo.group(1)) - is_logfile = True - - if num is None: continue - if num in self.buildCache.cache: continue - - if (is_logfile and num < earliest_log) or num < earliest_build: - pathname = os.path.join(self.basedir, filename) - log.msg("pruning '%s'" % pathname) - try: os.unlink(pathname) - except OSError: pass - - # IBuilderStatus methods - def getName(self): - # if builderstatus page does show not up without any reason then - # str(self.name) may be a workaround - return self.name - - def setDescription(self, description): - # used during reconfig - self.description = description - - def getDescription(self): - return self.description - - def getState(self): - return (self.currentBigState, self.currentBuilds) - - def getSlaves(self): - return [self.status.getSlave(name) for name in self.slavenames] - - def getPendingBuildRequestStatuses(self): - db = self.status.master.db - d = db.buildrequests.getBuildRequests(claimed=False, - buildername=self.name) - def make_statuses(brdicts): - return [BuildRequestStatus(self.name, brdict['brid'], - self.status) - for brdict in brdicts] - d.addCallback(make_statuses) - return d - - def getCurrentBuilds(self): - return self.currentBuilds - - def getLastFinishedBuild(self): - b = self.getBuild(-1) - if not (b and b.isFinished()): - b = self.getBuild(-2) - return b - - def setCategory(self, category): - # used during reconfig - self.category = category - - def getCategory(self): - return self.category - - def getBuild(self, number): - if number < 0: - number = self.nextBuildNumber + number - if number < 0 or number >= self.nextBuildNumber: - return None - - try: - return self.getBuildByNumber(number) - except IndexError: - return None - - def getEvent(self, number): - try: - return self.events[number] - except IndexError: - return None - - def _getBuildBranches(self, build): - return set([ ss.branch - for ss in build.getSourceStamps() ]) - - def generateFinishedBuilds(self, branches=[], - num_builds=None, - max_buildnum=None, - finished_before=None, - results=None, - max_search=200): - got = 0 - branches = set(branches) - for Nb in itertools.count(1): - if Nb > self.nextBuildNumber: - break - if Nb > max_search: - break - build = self.getBuild(-Nb) - if build is None: - continue - if max_buildnum is not None: - if build.getNumber() > max_buildnum: - continue - if not build.isFinished(): - continue - if finished_before is not None: - start, end = build.getTimes() - if end >= finished_before: - continue - # if we were asked to filter on branches, and none of the - # sourcestamps match, skip this build - if branches and not branches & self._getBuildBranches(build): - continue - if results is not None: - if build.getResults() not in results: - continue - got += 1 - yield build - if num_builds is not None: - if got >= num_builds: - return - - def eventGenerator(self, branches=[], categories=[], committers=[], minTime=0): - """This function creates a generator which will provide all of this - Builder's status events, starting with the most recent and - progressing backwards in time. """ - - # remember the oldest-to-earliest flow here. "next" means earlier. - - # TODO: interleave build steps and self.events by timestamp. - # TODO: um, I think we're already doing that. - - # TODO: there's probably something clever we could do here to - # interleave two event streams (one from self.getBuild and the other - # from self.getEvent), which would be simpler than this control flow - - eventIndex = -1 - e = self.getEvent(eventIndex) - branches = set(branches) - for Nb in range(1, self.nextBuildNumber+1): - b = self.getBuild(-Nb) - if not b: - # HACK: If this is the first build we are looking at, it is - # possible it's in progress but locked before it has written a - # pickle; in this case keep looking. - if Nb == 1: - continue - break - if b.getTimes()[0] < minTime: - break - # if we were asked to filter on branches, and none of the - # sourcestamps match, skip this build - if branches and not branches & self._getBuildBranches(b): - continue - if categories and not b.getBuilder().getCategory() in categories: - continue - if committers and not [True for c in b.getChanges() if c.who in committers]: - continue - steps = b.getSteps() - for Ns in range(1, len(steps)+1): - if steps[-Ns].started: - step_start = steps[-Ns].getTimes()[0] - while e is not None and e.getTimes()[0] > step_start: - yield e - eventIndex -= 1 - e = self.getEvent(eventIndex) - yield steps[-Ns] - yield b - while e is not None: - yield e - eventIndex -= 1 - e = self.getEvent(eventIndex) - if e and e.getTimes()[0] < minTime: - break - - def subscribe(self, receiver): - # will get builderChangedState, buildStarted, buildFinished, - # requestSubmitted, requestCancelled. Note that a request which is - # resubmitted (due to a slave disconnect) will cause requestSubmitted - # to be invoked multiple times. - self.watchers.append(receiver) - self.publishState(receiver) - # our parent Status provides requestSubmitted and requestCancelled - self.status._builder_subscribe(self.name, receiver) - - def unsubscribe(self, receiver): - self.watchers.remove(receiver) - self.status._builder_unsubscribe(self.name, receiver) - - ## Builder interface (methods called by the Builder which feeds us) - - def setSlavenames(self, names): - self.slavenames = names - - def addEvent(self, text=[]): - # this adds a duration event. When it is done, the user should call - # e.finish(). They can also mangle it by modifying .text - e = Event() - e.started = util.now() - e.text = text - self.events.append(e) - self.prune(events_only=True) - return e # they are free to mangle it further - - def addPointEvent(self, text=[]): - # this adds a point event, one which occurs as a single atomic - # instant of time. - e = Event() - e.started = util.now() - e.finished = 0 - e.text = text - self.events.append(e) - self.prune(events_only=True) - return e # for consistency, but they really shouldn't touch it - - def setBigState(self, state): - needToUpdate = state != self.currentBigState - self.currentBigState = state - if needToUpdate: - self.publishState() - - def publishState(self, target=None): - state = self.currentBigState - - if target is not None: - # unicast - target.builderChangedState(self.name, state) - return - for w in self.watchers: - try: - w.builderChangedState(self.name, state) - except: - log.msg("Exception caught publishing state to %r" % w) - log.err() - - def newBuild(self): - """The Builder has decided to start a build, but the Build object is - not yet ready to report status (it has not finished creating the - Steps). Create a BuildStatus object that it can use.""" - number = self.nextBuildNumber - self.nextBuildNumber += 1 - # TODO: self.saveYourself(), to make sure we don't forget about the - # build number we've just allocated. This is not quite as important - # as it was before we switch to determineNextBuildNumber, but I think - # it may still be useful to have the new build save itself. - s = BuildStatus(self, self.master, number) - s.waitUntilFinished().addCallback(self._buildFinished) - return s - - # buildStarted is called by our child BuildStatus instances - def buildStarted(self, s): - """Now the BuildStatus object is ready to go (it knows all of its - Steps, its ETA, etc), so it is safe to notify our watchers.""" - - assert s.builder is self # paranoia - assert s not in self.currentBuilds - self.currentBuilds.append(s) - self.buildCache.get(s.number, val=s) - - # now that the BuildStatus is prepared to answer queries, we can - # announce the new build to all our watchers - - for w in self.watchers: # TODO: maybe do this later? callLater(0)? - try: - receiver = w.buildStarted(self.getName(), s) - if receiver: - if type(receiver) == type(()): - s.subscribe(receiver[0], receiver[1]) - else: - s.subscribe(receiver) - d = s.waitUntilFinished() - d.addCallback(lambda s: s.unsubscribe(receiver)) - except: - log.msg("Exception caught notifying %r of buildStarted event" % w) - log.err() - - def _buildFinished(self, s): - assert s in self.currentBuilds - s.saveYourself() - self.currentBuilds.remove(s) - - name = self.getName() - results = s.getResults() - for w in self.watchers: - try: - w.buildFinished(name, s, results) - except: - log.msg("Exception caught notifying %r of buildFinished event" % w) - log.err() - - self.prune() # conserve disk - - - def asDict(self): - result = {} - # Constant - # TODO(maruel): Fix me. We don't want to leak the full path. - result['basedir'] = os.path.basename(self.basedir) - result['category'] = self.category - result['slaves'] = self.slavenames - result['schedulers'] = [ s.name - for s in self.status.master.allSchedulers() - if self.name in s.builderNames ] - #result['url'] = self.parent.getURLForThing(self) - # TODO(maruel): Add cache settings? Do we care? - - # Transient - # Collect build numbers. - # Important: Only grab the *cached* builds numbers to reduce I/O. - current_builds = [b.getNumber() for b in self.currentBuilds] - cached_builds = list(set(self.buildCache.keys() + current_builds)) - cached_builds.sort() - result['cachedBuilds'] = cached_builds - result['currentBuilds'] = current_builds - result['state'] = self.getState()[0] - # lies, but we don't have synchronous access to this info; use - # asDict_async instead - result['pendingBuilds'] = 0 - return result - - def asDict_async(self): - """Just like L{asDict}, but with a nonzero pendingBuilds.""" - result = self.asDict() - d = self.getPendingBuildRequestStatuses() - def combine(statuses): - result['pendingBuilds'] = len(statuses) - return result - d.addCallback(combine) - return d - - def getMetrics(self): - return self.botmaster.parent.metrics - -# vim: set ts=4 sts=4 sw=4 et: |