from __future__ import generators
import sys, time, os.path
import urllib
from buildbot import util
from buildbot import version
from buildbot.status.web.base import HtmlResource
#from buildbot.status.web.base import Box, HtmlResource, IBox, ICurrentBox, \
# ITopBox, td, build_get_class, path_to_build, path_to_step, map_branches
from buildbot.status.web.base import build_get_class
# set grid_css to the full pathname of the css file
if hasattr(sys, "frozen"):
# all 'data' files are in the directory of our executable
here = os.path.dirname(sys.executable)
grid_css = os.path.abspath(os.path.join(here, "grid.css"))
else:
# running from source; look for a sibling to __file__
up = os.path.dirname
grid_css = os.path.abspath(os.path.join(up(__file__), "grid.css"))
class ANYBRANCH: pass # a flag value, used below
class GridStatusResource(HtmlResource):
# TODO: docs
status = None
control = None
changemaster = None
def __init__(self, allowForce=True, css=None):
HtmlResource.__init__(self)
self.allowForce = allowForce
self.css = css or grid_css
def getTitle(self, request):
status = self.getStatus(request)
p = status.getProjectName()
if p:
return "BuildBot: %s" % p
else:
return "BuildBot"
def getChangemaster(self, request):
# TODO: this wants to go away, access it through IStatus
return request.site.buildbot_service.getChangeSvc()
# handle reloads through an http header
# TODO: send this as a real header, rather than a tag
def get_reload_time(self, request):
if "reload" in request.args:
try:
reload_time = int(request.args["reload"][0])
return max(reload_time, 15)
except ValueError:
pass
return None
def head(self, request):
head = ''
reload_time = self.get_reload_time(request)
if reload_time is not None:
head += '\n' % reload_time
return head
# def setBuildmaster(self, buildmaster):
# self.status = buildmaster.getStatus()
# if self.allowForce:
# self.control = interfaces.IControl(buildmaster)
# else:
# self.control = None
# self.changemaster = buildmaster.change_svc
#
# # try to set the page title
# p = self.status.getProjectName()
# if p:
# self.title = "BuildBot: %s" % p
#
def build_td(self, request, build):
if not build:
return '
| \n'
if build.isFinished():
# get the text and annotate the first line with a link
text = build.getText()
if not text: text = [ "(no information)" ]
if text == [ "build", "successful" ]: text = [ "OK" ]
else:
text = [ 'building' ]
name = build.getBuilder().getName()
number = build.getNumber()
url = "builders/%s/builds/%d" % (name, number)
text[0] = '%s' % (url, text[0])
text = '
\n'.join(text)
class_ = build_get_class(build)
return '%s | \n' % (class_, text)
def builder_td(self, request, builder):
state, builds = builder.getState()
# look for upcoming builds. We say the state is "waiting" if the
# builder is otherwise idle and there is a scheduler which tells us a
# build will be performed some time in the near future. TODO: this
# functionality used to be in BuilderStatus.. maybe this code should
# be merged back into it.
upcoming = []
builderName = builder.getName()
for s in self.getStatus(request).getSchedulers():
if builderName in s.listBuilderNames():
upcoming.extend(s.getPendingBuildTimes())
if state == "idle" and upcoming:
state = "waiting"
# TODO: for now, this pending/upcoming stuff is in the "current
# activity" box, but really it should go into a "next activity" row
# instead. The only times it should show up in "current activity" is
# when the builder is otherwise idle.
# are any builds pending? (waiting for a slave to be free)
url = 'builders/%s/' % urllib.quote(builder.getName(), safe='')
text = '%s' % (url, builder.getName())
pbs = builder.getPendingBuilds()
if state != 'idle' or pbs:
if pbs:
text += "
(%s with %d pending)" % (state, len(pbs))
else:
text += "
(%s)" % state
return '%s | \n' % \
(state, text)
def stamp_td(self, stamp):
text = stamp.getText()
return '%s | \n' % \
"
".join(text)
def body(self, request):
"This method builds the main waterfall display."
# get url parameters
numBuilds = int(request.args.get("width", [5])[0])
categories = request.args.get("category", [])
branch = request.args.get("branch", [ANYBRANCH])[0]
if branch == 'trunk': branch = None
# and the data we want to render
status = self.getStatus(request)
stamps = self.getRecentSourcestamps(status, numBuilds, categories, branch)
projectURL = status.getProjectURL()
projectName = status.getProjectName()
data = '\n'
data += '\n'
data += '%s' % (projectURL, projectName)
if categories:
if len(categories) > 1:
data += '\n Categories: %s' % (' '.join(categories))
else:
data += '\n Category: %s' % categories[0]
if branch != ANYBRANCH:
data += '\n Branch: %s' % (branch or 'trunk')
data += ' | \n'
for stamp in stamps:
data += self.stamp_td(stamp)
data += '
\n'
sortedBuilderNames = status.getBuilderNames()[:]
sortedBuilderNames.sort()
for bn in sortedBuilderNames:
builds = [None] * len(stamps)
builder = status.getBuilder(bn)
if categories and builder.category not in categories:
continue
build = builder.getBuild(-1)
while build and None in builds:
ss = build.getSourceStamp(absolute=True)
for i in range(len(stamps)):
if ss == stamps[i] and builds[i] is None:
builds[i] = build
build = build.getPreviousBuild()
data += '\n'
data += self.builder_td(request, builder)
for build in builds:
data += self.build_td(request, build)
data += '
\n'
data += '
\n'
# TODO: this stuff should be generated by a template of some sort
data += '
\n'
return data
def getRecentSourcestamps(self, status, numBuilds, categories, branch):
"""
get a list of the most recent NUMBUILDS SourceStamp tuples, sorted
by the earliest start we've seen for them
"""
# TODO: use baseweb's getLastNBuilds?
sourcestamps = { } # { ss-tuple : earliest time }
for bn in status.getBuilderNames():
builder = status.getBuilder(bn)
if categories and builder.category not in categories:
continue
build = builder.getBuild(-1)
while build:
ss = build.getSourceStamp(absolute=True)
start = build.getTimes()[0]
build = build.getPreviousBuild()
# skip un-started builds
if not start: continue
# skip non-matching branches
if branch != ANYBRANCH and ss.branch != branch: continue
sourcestamps[ss] = min(sourcestamps.get(ss, sys.maxint), start)
# now sort those and take the NUMBUILDS most recent
sourcestamps = sourcestamps.items()
sourcestamps.sort(lambda x, y: cmp(x[1], y[1]))
sourcestamps = map(lambda tup : tup[0], sourcestamps)
sourcestamps = sourcestamps[-numBuilds:]
return sourcestamps