|
# 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
# these lists/dicts are defined here so that unserialized instances have # (empty) values. They are set in __init__ to new objects to make sure # each instance gets its own copy.
""" @type parent: L{BuilderStatus} @type number: int """
def __repr__(self): return "<%s #%s>" % (self.__class__.__name__, self.number)
# IBuildStatus
""" @rtype: L{BuilderStatus} """
if self.number == 0: return None return self.builder.getBuild(self.number-1)
# For backwards compatibility all_got_revisions is a string if codebases # are not used. Convert to the default internal type (dict) all_got_revisions = {'': all_got_revisions}
else: # always make a new instance else: # No absolute revision information available # Probably build has been stopped before ending all sourcesteps # Return a clone with original revision
return self.reason
return self.changes
revs = [] for c in self.changes: rev = str(c.revision) if rev > 7: # for long hashes rev = rev[:7] revs.append(rev) return ", ".join(revs)
return self.blamelist
# TODO: the Builder should add others: sheriffs, domain-owners return self.blamelist + self.properties.getProperty('owners', [])
"""Return a list of IBuildStepStatus objects. For invariant builds (those which always use the same set of Steps), this should be the complete list, however some of the steps may not have started yet (step.getTimes()[0] will be None). For variant builds, this may not be complete (asking again later may give you more of them)."""
return (self.started, self.finished)
"""Summarize the named statistic over all steps in which it exists, using combination_fn and initial_value to combine multiple results into a single result. This translates to a call to Python's X{reduce}:: return reduce(summary_fn, step_stats_list, initial_value) """ step_stats_list = [ st.getStatistic(name) for st in self.steps if st.hasStatistic(name) ] if initial_value is self._sentinel: return reduce(summary_fn, step_stats_list) else: return reduce(summary_fn, step_stats_list, initial_value)
return (self.finished is not None)
d = defer.succeed(self) else:
# while the build is running, the following methods make sense. # Afterwards they return None
if self.finished is not None: return None if not self.progress: return None eta = self.progress.eta() if eta is None: return None return eta - util.now()
return self.currentStep
# Once you know the build has finished, the following methods are legal. # Before ths build has finished, they all return None.
text = [] text.extend(self.text) for s in self.steps: text.extend(s.text2) return text
return self.slavename
return self.testResults
# TODO: steps should contribute significant logs instead of this # hack, which returns every log from every step. The logs should get # names like "compile" and "test" instead of "compile.output" logs = [] for s in self.steps: for loog in s.getLogs(): logs.append(loog) return logs
# subscription interface
# will receive stepStarted and stepFinished messages # and maybe buildETAUpdate self.watchers.append(receiver) if updateInterval is not None: self.sendETAUpdate(receiver, updateInterval)
self.updates[receiver] = None ETA = self.getETA() if ETA is not None: receiver.buildETAUpdate(self, self.getETA()) # they might have unsubscribed during buildETAUpdate if receiver in self.watchers: self.updates[receiver] = reactor.callLater(updateInterval, self.sendETAUpdate, receiver, updateInterval)
if receiver in self.watchers: self.watchers.remove(receiver) if receiver in self.updates: if self.updates[receiver] is not None: self.updates[receiver].cancel() del self.updates[receiver]
# methods for the base.Build to invoke
"""The Build is setting up, and has added a new BuildStep to its list. Create a BuildStepStatus object to which it can send status updates."""
self.testResults[result.getName()] = result
self.sources = sourceStamps self.changes = [] for source in self.sources: self.changes.extend(source.changes)
self.reason = reason self.blamelist = blamelist self.progress = progress
"""The Build has been set up and is about to be started. It can now be safely queried, so it is time to announce the new build."""
# now that we're ready to report status, let the BuilderStatus tell # the world about us
self.slavename = slavename
assert isinstance(text, (list, tuple)) self.text = text self.results = results
if self.updates[r] is not None: self.updates[r].cancel() del self.updates[r]
# methods called by our BuildStepStatus children
receiver = w.stepStarted(self, step) if receiver: if type(receiver) == type(()): step.subscribe(receiver[0], receiver[1]) else: step.subscribe(receiver) d = step.waitUntilFinished() d.addCallback(lambda step: step.unsubscribe(receiver))
results = step.getResults() for w in self.watchers: w.stepFinished(self, step, results)
# methods called by our BuilderStatus parent
# this build is very old: remove the build steps too self.steps = []
# persistence stuff
"""Return a filename (relative to the Builder's base directory) where the logfile's contents can be stored uniquely.
The base filename is made by combining our build number, the Step's name, and the log's name, then removing unsuitable characters. The filename is then made unique by appending _0, _1, etc, until it does not collide with any other logfile.
These files are kept in the Builder's basedir (rather than a per-Build subdirectory) because that makes cleanup easier: cron and find will help get rid of the old logs, but the empty directories are more of a hassle to remove."""
# now make it unique for step in self.steps for l in step.getLogs() if l.filename]: filename = "%s_%d" % (starting_filename, unique_counter) unique_counter += 1
# for now, a serialized Build is always "finished". We will never # save unfinished builds. d['finished'] = util.now() # TODO: push an "interrupted" step so it is clear that the build # was interrupted. The builder will have a 'shutdown' event, but # someone looking at just this build will be confused as to why # the last log is truncated. 'master' ]:
self.builder = builder self.master = master for step in self.steps: step.setProcessObjects(self, master) if hasattr(self, "sourceStamp"): # the old .sourceStamp attribute wasn't actually very useful maxChangeNumber, patch = self.sourceStamp changes = getattr(self, 'changes', []) source = sourcestamp.SourceStamp(branch=None, revision=None, patch=patch, changes=changes) self.source = source self.changes = source.changes del self.sourceStamp self.wasUpgraded = True
self.properties = {} self.wasUpgraded = True
# in version 3, self.properties became a Properties object propdict = self.properties self.properties = properties.Properties() self.properties.update(propdict, "Upgrade from previous version") self.wasUpgraded = True
# buildstatus contains list of sourcestamps, convert single to list self.sources = [self.source] del self.source
# check that all logfiles exist, and remove references to any that # have been deleted (e.g., by purge()) for s in self.steps: s.checkLogfiles()
# leftover from 0.5.0, which stored builds in directories shutil.rmtree(filename, ignore_errors=True) if runtime.platformType == 'win32': # windows cannot rename a file on top of an existing one, so # fall back to delete-first. There are ways this can fail and # lose the builder's history, so we avoid using it in the # general (non-windows) case if os.path.exists(filename): os.unlink(filename) except: log.msg("unable to save build %s-#%d" % (self.builder.name, self.number)) log.err()
result = {} # Constant result['builderName'] = self.builder.name result['number'] = self.getNumber() result['sourceStamps'] = [ss.asDict() for ss in self.getSourceStamps()] result['reason'] = self.getReason() result['blame'] = self.getResponsibleUsers()
# Transient result['properties'] = self.getProperties().asList() result['times'] = self.getTimes() result['text'] = self.getText() result['results'] = self.getResults() result['slave'] = self.getSlavename() # TODO(maruel): Add. #result['test_results'] = self.getTestResults() result['logs'] = [[l.getName(), self.builder.status.getURLForThing(l)] for l in self.getLogs()] result['eta'] = self.getETA() result['steps'] = [bss.asDict() for bss in self.steps] if self.getCurrentStep(): result['currentStep'] = self.getCurrentStep().asDict() else: result['currentStep'] = None return result
BuildStatus, interfaces.IProperties) |