Author: Armin Rigo <[email protected]>
Branch:
Changeset: r584:b0410106615e
Date: 2011-08-24 11:40 +0200
http://bitbucket.org/pypy/buildbot/changeset/b0410106615e/
Log: merge heads
diff --git a/bot2/pypybuildbot/builds.py b/bot2/pypybuildbot/builds.py
--- a/bot2/pypybuildbot/builds.py
+++ b/bot2/pypybuildbot/builds.py
@@ -37,8 +37,8 @@
branch = properties['branch']
if branch is None:
branch = 'trunk'
- masterdest = properties.render(self.masterdest)
- masterdest = os.path.expanduser(masterdest)
+ #masterdest = properties.render(self.masterdest)
+ masterdest = os.path.expanduser(self.masterdest)
if branch.startswith('/'):
branch = branch[1:]
# workaround for os.path.join
@@ -51,7 +51,7 @@
assert '%' not in symname
self.symlinkname = os.path.join(masterdest, symname)
#
- basename = WithProperties(self.basename).render(properties)
+ basename = WithProperties(self.basename).getRenderingFor(self.build)
self.masterdest = os.path.join(masterdest, basename)
#
transfer.FileUpload.start(self)
diff --git a/bot2/pypybuildbot/ircbot.py b/bot2/pypybuildbot/ircbot.py
new file mode 100644
--- /dev/null
+++ b/bot2/pypybuildbot/ircbot.py
@@ -0,0 +1,106 @@
+"""
+Monkeypatch buildbot.status.words.Contact: this is the easiest (only?) way to
+customize the messages sent by the IRC bot. Tested with buildbot 0.8.4p2,
+might break in future versions.
+
+If you uncomment out this code, things will still work and you'll just loose
+the customized IRC messages.
+"""
+
+import re
+from buildbot.status.words import Contact, IRC, log
+
+USE_COLOR_CODES = True
+GREEN = '\x033'
+RED = '\x034'
+AZURE = '\x0311'
+BLUE = '\x0312'
+PURPLE = '\x0313'
+GRAY = '\x0315'
+BOLD = '\x02'
+def color(code, s):
+ if USE_COLOR_CODES:
+ return '%s%s\x0F' % (code, s)
+ return s
+
+def extract_username(build):
+ regexp = r"The web-page 'force build' button was pressed by '(.*)': .*"
+ match = re.match(regexp, build.getReason())
+ if match:
+ return match.group(1)
+ return None
+
+
+def get_description_for_build(url, build):
+ url = color(GRAY, url) # in gray
+ infos = []
+ username = extract_username(build)
+ if username:
+ infos.append(color(BLUE, username)) # in blue
+ #
+ branch = build.source.branch
+ if branch:
+ infos.append(color(BOLD, branch)) # in bold
+ #
+ if infos:
+ return '%s [%s]' % (url, ', '.join(infos))
+ else:
+ return url
+
+def buildStarted(self, builderName, build):
+ builder = build.getBuilder()
+ log.msg('[Contact] Builder %r in category %s started' % (builder,
builder.category))
+
+ # only notify about builders we are interested in
+
+ if (self.channel.categories != None and
+ builder.category not in self.channel.categories):
+ log.msg('Not notifying for a build in the wrong category')
+ return
+
+ if not self.notify_for('started'):
+ log.msg('Not notifying for a build when started-notification disabled')
+ return
+
+ buildurl = self.channel.status.getURLForThing(build)
+ descr = get_description_for_build(buildurl, build)
+ msg = "Started: %s" % descr
+ self.send(msg)
+
+
+def buildFinished(self, builderName, build, results):
+ builder = build.getBuilder()
+
+ # only notify about builders we are interested in
+ log.msg('[Contact] builder %r in category %s finished' % (builder,
builder.category))
+
+ if (self.channel.categories != None and
+ builder.category not in self.channel.categories):
+ return
+
+ if not self.notify_for_finished(build):
+ return
+
+ buildurl = self.channel.status.getURLForThing(build)
+ descr = get_description_for_build(buildurl, build)
+ result = self.results_descriptions.get(build.getResults(), "Finished ??")
+ if result == 'Success':
+ result = color(BOLD+GREEN, result)
+ elif result == 'Exception':
+ result = color(BOLD+PURPLE, result)
+ else:
+ result = color(BOLD+RED, result)
+ msg = "%s: %s" % (result, descr)
+ self.send(msg)
+
+Contact.buildStarted = buildStarted
+Contact.buildFinished = buildFinished
+
+
+## def send_message(message, test=False):
+## import subprocess
+## return subprocess.call([
+## '/tmp/commit-bot/message',
+## '#buildbot-test',
+## message])
+## send_message(color(BOLD+PURPLE, 'ciao'))
diff --git a/bot2/pypybuildbot/master.py b/bot2/pypybuildbot/master.py
--- a/bot2/pypybuildbot/master.py
+++ b/bot2/pypybuildbot/master.py
@@ -1,38 +1,13 @@
+import getpass
from buildbot.scheduler import Nightly
from buildbot.buildslave import BuildSlave
from buildbot.status.html import WebStatus
from buildbot.process.builder import Builder
from pypybuildbot.pypylist import PyPyList
-
-# I really wanted to pass logPath to Site
-from twisted.web.server import Site
-class LoggingSite(Site):
- def __init__(self, *a, **kw):
- Site.__init__(self, logPath='httpd.log', *a, **kw)
-from twisted.web import server
-if server.Site.__name__ == 'Site':
- server.Site = LoggingSite
-# So I did.
-
-
-# The button Resubmit Build is quite confusing, so disable it
-from buildbot.status.web.build import StatusResourceBuild
-StatusResourceBuild_init = StatusResourceBuild.__init__
-def my_init(self, build_status, build_control, builder_control):
- StatusResourceBuild_init(self, build_status, build_control, None)
-if StatusResourceBuild.__init__.__name__ == '__init__':
- StatusResourceBuild.__init__ = my_init
-# Disabled.
-
-# Disable pinging, as it seems to deadlock the client
-from buildbot.status.web.builder import StatusResourceBuilder
-def my_ping(self, req):
- raise Exception("pinging is disabled, as it seems to deadlock clients")
-if StatusResourceBuilder.ping.__name__ == 'ping':
- StatusResourceBuilder.ping = my_ping
-# Disabled.
+from pypybuildbot.ircbot import IRC # side effects
# Forbid "force build" with empty user name
+from buildbot.status.web.builder import StatusResourceBuilder
def my_force(self, req):
name = req.args.get("username", [""])[0]
assert name, "Please write your name in the corresponding field."
@@ -42,57 +17,19 @@
StatusResourceBuilder.force = my_force
# Done
-# Add a link from the builder page to the summary page
-def my_body(self, req):
- data = _previous_body(self, req)
- MARKER = 'waterfall</a>)'
- i = data.find(MARKER)
- if i >= 0:
- from twisted.web import html
- i += len(MARKER)
- b = self.builder_status
- url =
self.path_to_root(req)+"summary?builder="+html.escape(b.getName())
- data = '%s (<a href="%s">view in summary</a>)%s' % (
- data[:i],
- url,
- data[i:])
- return data
-_previous_body = StatusResourceBuilder.body
-if _previous_body.__name__ == 'body':
- StatusResourceBuilder.body = my_body
-# Done
-
-# Add a similar link from the build page to the summary page
-def my_body_2(self, req):
- data = _previous_body_2(self, req)
- MARKER1 = '<h2>Results'
- MARKER2 = '<h2>SourceStamp'
- i1 = data.find(MARKER1)
- i2 = data.find(MARKER2)
- if i1 >= 0 and i2 >= 0:
- from twisted.web import html
- b = self.build_status
- ss = b.getSourceStamp()
- branch = ss.branch or '<trunk>'
- builder_name = b.getBuilder().getName()
- url = (self.path_to_root(req) +
- "summary?builder=" + html.escape(builder_name) +
- "&branch=" + html.escape(branch))
- data = '%s (<a href="%s">view in summary</a>)\n\n%s'%
(
- data[:i2],
- url,
- data[i2:])
- return data
-_previous_body_2 = StatusResourceBuild.body
-if _previous_body_2.__name__ == 'body':
- StatusResourceBuild.body = my_body_2
-
-# Picking a random slave is not really what we want;
-# let's pick the first available one instead.
-Builder.CHOOSE_SLAVES_RANDOMLY = False
-
+if getpass.getuser() == 'antocuni':
+ channel = '#buildbot-test'
+else:
+ channel = '#pypy'
status = WebStatus(httpPortNumber, allowForce=True)
+ircbot = IRC(host="irc.freenode.org",
+ nick="bbot2",
+ channels=[channel],
+ notify_events={
+ 'started': 1,
+ 'finished': 1,
+ })
# pypy test summary page
summary = load('pypybuildbot.summary')
@@ -254,7 +191,7 @@
JITBENCH, # on tannit32, uses 1 core (in part
exclusively)
JITBENCH64, # on tannit64, uses 1 core (in part
exclusively)
MACOSX32, # on minime
- ], hour=0, minute=0),
+ ], branch=None, hour=0, minute=0),
#
# then, we schedule all the rest. The locks will take care not to run
# all of them in parallel
@@ -271,10 +208,10 @@
JITWIN32, # on bigboard
STACKLESSAPPLVLFREEBSD64, # on headless
JITMACOSX64, # on mvt's machine
- ], hour=3, minute=0)
+ ], branch=None, hour=3, minute=0)
],
- 'status': [status],
+ 'status': [status, ircbot],
'slaves': [BuildSlave(name, password)
for (name, password)
@@ -287,11 +224,7 @@
"factory": pypyOwnTestFactory,
"category": 'linux32',
# this build needs 4 CPUs
- "locks": [TannitCPU.access('counting'),
- TannitCPU.access('counting'),
- TannitCPU.access('counting'),
- TannitCPU.access('counting'),
- ],
+ "locks": [TannitCPU.access('exclusive')],
},
{"name": LINUX64,
"slavenames": ["tannit64"],
@@ -299,23 +232,7 @@
"factory": pypyOwnTestFactory,
"category": 'linux64',
# this build needs 4 CPUs
- "locks": [TannitCPU.access('counting'),
- TannitCPU.access('counting'),
- TannitCPU.access('counting'),
- TannitCPU.access('counting'),
- ],
- },
- {"name": MACOSX32,
- "slavenames": ["minime"],
- "builddir": MACOSX32,
- "factory": pypyOwnTestFactory,
- "category": 'mac32'
- },
- {"name": WIN32,
- "slavenames": ["bigboard"],
- "builddir": WIN32,
- "factory": pypyOwnTestFactoryWin,
- "category": 'win32'
+ "locks": [TannitCPU.access('exclusive')],
},
{"name": APPLVLLINUX32,
"slavenames": ["bigdogvm1", "tannit32"],
@@ -345,18 +262,6 @@
"category": 'linux32',
"locks": [TannitCPU.access('counting')],
},
- {"name": APPLVLWIN32,
- "slavenames": ["bigboard"],
- "builddir": APPLVLWIN32,
- "factory": pypyTranslatedAppLevelTestFactoryWin,
- "category": "win32"
- },
- {"name" : STACKLESSAPPLVLFREEBSD64,
- "slavenames": ['headless'],
- 'builddir' : STACKLESSAPPLVLFREEBSD64,
- 'factory' : pypyStacklessTranslatedAppLevelTestFactory,
- "category": 'freebsd64-stackless'
- },
{"name" : JITLINUX32,
"slavenames": ["bigdogvm1", "tannit32"],
'builddir' : JITLINUX32,
@@ -371,18 +276,6 @@
'category': 'linux64',
"locks": [TannitCPU.access('counting')],
},
- {"name" : JITMACOSX64,
- "slavenames": ["macmini-mvt", "xerxes"],
- 'builddir' : JITMACOSX64,
- 'factory' : pypyJITTranslatedTestFactoryOSX64,
- 'category' : 'mac64',
- },
- {"name" : JITWIN32,
- "slavenames": ["bigboard"],
- 'builddir' : JITWIN32,
- 'factory' : pypyJITTranslatedTestFactoryWin,
- 'category' : 'win32',
- },
{"name": JITONLYLINUX32,
"slavenames": ["tannit32", "bigdogvm1"],
"builddir": JITONLYLINUX32,
@@ -404,6 +297,42 @@
"category": "benchmark-run",
# the locks are acquired with fine grain inside the build
},
+ {"name": MACOSX32,
+ "slavenames": ["minime"],
+ "builddir": MACOSX32,
+ "factory": pypyOwnTestFactory,
+ "category": 'mac32'
+ },
+ {"name" : JITMACOSX64,
+ "slavenames": ["macmini-mvt", "xerxes"],
+ 'builddir' : JITMACOSX64,
+ 'factory' : pypyJITTranslatedTestFactoryOSX64,
+ 'category' : 'mac64',
+ },
+ {"name": WIN32,
+ "slavenames": ["bigboard"],
+ "builddir": WIN32,
+ "factory": pypyOwnTestFactoryWin,
+ "category": 'win32'
+ },
+ {"name": APPLVLWIN32,
+ "slavenames": ["bigboard"],
+ "builddir": APPLVLWIN32,
+ "factory": pypyTranslatedAppLevelTestFactoryWin,
+ "category": "win32"
+ },
+ {"name" : JITWIN32,
+ "slavenames": ["bigboard"],
+ 'builddir' : JITWIN32,
+ 'factory' : pypyJITTranslatedTestFactoryWin,
+ 'category' : 'win32',
+ },
+ {"name" : STACKLESSAPPLVLFREEBSD64,
+ "slavenames": ['headless'],
+ 'builddir' : STACKLESSAPPLVLFREEBSD64,
+ 'factory' : pypyStacklessTranslatedAppLevelTestFactory,
+ "category": 'freebsd64-stackless'
+ },
],
'buildbotURL': 'http://buildbot.pypy.org/', # with a trailing '/'!
diff --git a/bot2/pypybuildbot/summary.py b/bot2/pypybuildbot/summary.py
--- a/bot2/pypybuildbot/summary.py
+++ b/bot2/pypybuildbot/summary.py
@@ -640,12 +640,7 @@
i = rev.index(':')
return (2, int(rev[:i]), rev)
# unknown
- return (3, rev)
-
-HEAD_ELEMENTS = [
- '<title>%(title)s</title>',
- '<link href="%(root)ssummary.css" rel="stylesheet" type="text/css" />',
- ]
+ return (0, rev)
class Summary(HtmlResource):
@@ -656,13 +651,12 @@
self.categories = categories
self.branch_order_prefixes = branch_order_prefixes
- def content(self, request):
- old_head_elements = request.site.buildbot_service.head_elements
- self.head_elements = HEAD_ELEMENTS
- try:
- return HtmlResource.content(self, request)
- finally:
- request.site.buildbot_service.head_elements = old_head_elements
+ def content(self, req, context):
+ body = self.body(req)
+ context['content'] = body
+ template = req.site.buildbot_service.templates.get_template(
+ "summary.html")
+ return template.render(**context)
def getTitle(self, request):
status = self.getStatus(request)
@@ -877,39 +871,3 @@
trunk_vs_any = html.div(trunk_vs_any_anchor,
style="position: absolute; right: 5%;")
return trunk_vs_any.unicode() + page.render()
-
- def head(self, request):
- return """
- <script language=javascript type='text/javascript'>
- hiddenstates = [ ];
- function togglestate(a, c) {
- var start = "a" + a + "c";
- var link = document.getElementById(start + c);
- var state = hiddenstates[a];
- if (!state) state = 0;
- if (state & c) {
- state = state - c;
- link.textContent = '-';
- }
- else {
- state = state | c;
- link.textContent = 'H';
- }
- hiddenstates[a] = state;
- var items = document.getElementsByTagName('span');
- var i = items.length;
- var toggle = "";
- while (i > 0) {
- i--;
- var span = items.item(i);
- if (span.className.substr(0, start.length) == start) {
- var k = span.className.substr(start.length);
- if ((state & k) == k)
- span.style.display = 'none';
- else
- span.style.display = 'block';
- }
- }
- }
- </script>
- """
diff --git a/bot2/pypybuildbot/test/test_builds.py
b/bot2/pypybuildbot/test/test_builds.py
--- a/bot2/pypybuildbot/test/test_builds.py
+++ b/bot2/pypybuildbot/test/test_builds.py
@@ -3,6 +3,11 @@
from pypybuildbot import builds
class FakeProperties(object):
+
+ def __init__(self):
+ from buildbot.process.properties import PropertyMap
+ self.pmap = PropertyMap(self)
+
def __getitem__(self, item):
if item == 'branch':
return None
@@ -14,11 +19,14 @@
def render(self, x):
return x
-class FakePropertyBuilder(object):
+class FakeBuild(object):
slaveEnvironment = None
+
+ def __init__(self):
+ self.properties = FakeProperties()
def getProperties(self):
- return FakeProperties()
+ return self.properties
def getSlaveCommandVersion(self, *args):
return 3
@@ -46,7 +54,8 @@
assert rebuiltTranslate.command[-len(expected):] == expected
- rebuiltTranslate.build = FakePropertyBuilder()
+ rebuiltTranslate.build = FakeBuild()
+ rebuiltTranslate.setBuild(rebuiltTranslate.build)
rebuiltTranslate.startCommand = lambda *args: None
rebuiltTranslate.start()
@@ -57,7 +66,7 @@
blocksize=100)
factory, kw = inst.factory
rebuilt = factory(**kw)
- rebuilt.build = FakePropertyBuilder()
+ rebuilt.build = FakeBuild()
rebuilt.step_status = FakeStepStatus()
rebuilt.runCommand = lambda *args: FakeDeferred()
rebuilt.start()
diff --git a/bot2/pypybuildbot/test/test_ircbot.py
b/bot2/pypybuildbot/test/test_ircbot.py
new file mode 100644
--- /dev/null
+++ b/bot2/pypybuildbot/test/test_ircbot.py
@@ -0,0 +1,50 @@
+from pypybuildbot import ircbot
+
+def setup_module(mod):
+ ircbot.USE_COLOR_CODES = False
+
+def teardown_module(mod):
+ ircbot.USE_COLOR_CODES = True
+
+class FakeBuild(object):
+
+ def __init__(self, reason=None, source=None):
+ self.reason = reason
+ self.source = source
+
+ def getReason(self):
+ return self.reason
+
+ def getSourceStamp(self):
+ return self.source
+
+class FakeSource(object):
+
+ def __init__(self, branch):
+ self.branch = branch
+
+def test_extract_username():
+ a = FakeBuild("The web-page 'force build' button was pressed by
'antocuni': foo")
+ b = FakeBuild("The web-page 'force build' button was ...")
+ assert ircbot.extract_username(a) == 'antocuni'
+ assert ircbot.extract_username(b) is None
+
+
+def test_get_description_for_build():
+ a = FakeBuild('foobar', source=FakeSource(None))
+ msg = ircbot.get_description_for_build("http://myurl", a)
+ assert msg == "http://myurl"
+
+ a = FakeBuild("The web-page 'force build' button was pressed by
'antocuni': foo",
+ source=FakeSource(None))
+ msg = ircbot.get_description_for_build("http://myurl", a)
+ assert msg == "http://myurl [antocuni]"
+
+ a = FakeBuild('foobar', source=FakeSource('mybranch'))
+ msg = ircbot.get_description_for_build("http://myurl", a)
+ assert msg == "http://myurl [mybranch]"
+
+ a = FakeBuild("The web-page 'force build' button was pressed by
'antocuni': foo",
+ source=FakeSource('mybranch'))
+ msg = ircbot.get_description_for_build("http://myurl", a)
+ assert msg == "http://myurl [antocuni, mybranch]"
diff --git a/bot2/pypybuildbot/test/test_summary.py
b/bot2/pypybuildbot/test/test_summary.py
--- a/bot2/pypybuildbot/test/test_summary.py
+++ b/bot2/pypybuildbot/test/test_summary.py
@@ -357,14 +357,26 @@
'factory': process_factory.BuildFactory() }
return process_builder.Builder(setup, status)
-class FakeRequest(object):
- def __init__(self, builders, args={}):
- status = status_builder.Status(self, '/tmp')
- status.basedir = None
- self.status = status
- self.args = args
+class FakeMaster(object):
+ basedir = None
+ buildbotURL = "http://buildbot/"
+ def __init__(self, builders):
+ self.botmaster = FakeBotMaster(builders)
+
+ def subscribeToBuildsetCompletions(self, callback):
+ pass
+
+ def subscribeToBuildsets(self, callback):
+ pass
+
+ def subscribeToBuildRequests(self, callback):
+ pass
+
+class FakeBotMaster(object):
+
+ def __init__(self, builders):
self.builderNames = []
self.builders = {}
for builder in builders:
@@ -372,14 +384,28 @@
self.builderNames.append(name)
self.builders[name] = _BuilderToStatus(builder)
- self.site = self
- self.buildbot_service = self
- self.parent = self
- self.buildbotURL = "http://buildbot/"
+class FakeSite(object):
+
+ def __init__(self, status):
+ self.buildbot_service = FakeService(status)
+
+class FakeService(object):
+
+ def __init__(self, status):
+ self.status = status
def getStatus(self):
return self.status
+class FakeRequest(object):
+
+ def __init__(self, builders, args={}):
+ master = FakeMaster(builders)
+ status = status_builder.Status(master)
+ self.args = args
+ self.site = FakeSite(status)
+
+
def witness_cat_branch(summary):
ref = [None]
recentRuns = summary.recentRuns
diff --git a/master/buildbot.tac b/master/buildbot.tac
--- a/master/buildbot.tac
+++ b/master/buildbot.tac
@@ -12,8 +12,8 @@
# ---------------------------------------------------------------
configfile = r'master.cfg'
-rotateLength = 1000000
-maxRotatedFiles = None
+rotateLength = 1024*1024
+maxRotatedFiles = 100
application = service.Application('buildmaster')
try:
diff --git a/master/public_html/default.css b/master/public_html/default.css
new file mode 100644
--- /dev/null
+++ b/master/public_html/default.css
@@ -0,0 +1,544 @@
+body.interface {
+ margin-left: 30px;
+ margin-right: 30px;
+ margin-top: 20px;
+ margin-bottom: 50px;
+ padding: 0;
+ font-family: Verdana, sans-serif;
+ font-size: 10px;
+ background-color: #fff;
+ color: #333;
+}
+
+a:link,a:visited,a:active {
+ color: #444;
+}
+
+table {
+ border-spacing: 1px 1px;
+}
+
+table td {
+ padding: 3px 4px 3px 4px;
+ text-align: center;
+}
+
+.Project {
+ min-width: 6em;
+}
+
+.LastBuild,.Activity {
+ padding: 0 0 0 4px;
+}
+
+.LastBuild,.Activity,.Builder,.BuildStep {
+ min-width: 5em;
+}
+
+/* Chromium Specific styles */
+div.BuildResultInfo {
+ color: #444;
+}
+
+div.Announcement {
+ margin-bottom: 1em;
+}
+
+div.Announcement>a:hover {
+ color: black;
+}
+
+div.Announcement>div.Notice {
+ background-color: #afdaff;
+ padding: 0.5em;
+ font-size: 16px;
+ text-align: center;
+}
+
+div.Announcement>div.Open {
+ border: 3px solid #8fdf5f;
+ padding: 0.5em;
+ font-size: 16px;
+ text-align: center;
+}
+
+div.Announcement>div.Closed {
+ border: 5px solid #e98080;
+ padding: 0.5em;
+ font-size: 24px;
+ font-weight: bold;
+ text-align: center;
+}
+
+td.Time {
+ color: #000;
+ border-bottom: 1px solid #aaa;
+ background-color: #eee;
+}
+
+td.Activity,td.Change,td.Builder {
+ color: #333333;
+ background-color: #CCCCCC;
+}
+
+td.Change {
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+}
+
+td.Event {
+ color: #777;
+ background-color: #ddd;
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+}
+
+td.Activity {
+ border-top-left-radius: 10px;
+ -webkit-border-top-left-radius: 10px;
+ -moz-border-radius-topleft: 10px;
+ min-height: 20px;
+ padding: 2px 0 2px 0;
+}
+
+td.idle,td.waiting,td.offline,td.building {
+ border-top-left-radius: 0px;
+ -webkit-border-top-left-radius: 0px;
+ -moz-border-radius-topleft: 0px;
+}
+
+.LastBuild {
+ border-top-left-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+ -moz-border-radius-topleft: 5px;
+ border-top-right-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ -moz-border-radius-topright: 5px;
+}
+
+/* Console view styles */
+td.DevRev {
+ padding: 4px 8px 4px 8px;
+ color: #333333;
+ border-top-left-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+ -moz-border-radius-topleft: 5px;
+ background-color: #eee;
+ width: 1%;
+}
+
+td.DevRevCollapse {
+ border-bottom-left-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -moz-border-radius-bottomleft: 5px;
+}
+
+td.DevName {
+ padding: 4px 8px 4px 8px;
+ color: #333333;
+ background-color: #eee;
+ width: 1%;
+ text-align: left;
+}
+
+td.DevStatus {
+ padding: 4px 4px 4px 4px;
+ color: #333333;
+ background-color: #eee;
+}
+
+td.DevSlave {
+ padding: 4px 4px 4px 4px;
+ color: #333333;
+ background-color: #eee;
+}
+
+td.first {
+ border-top-left-radius: 5px;
+ -webkit-border-top-left-radius: 5px;
+ -moz-border-radius-topleft: 5px;
+}
+
+td.last {
+ border-top-right-radius: 5px;
+ -webkit-border-top-right-radius: 5px;
+ -moz-border-radius-topright: 5px;
+}
+
+td.DevStatusCategory {
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-width: 1px;
+ border-style: solid;
+}
+
+td.DevStatusCollapse {
+ border-bottom-right-radius: 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -moz-border-radius-bottomright: 5px;
+}
+
+td.DevDetails {
+ font-weight: normal;
+ padding: 8px 8px 8px 8px;
+ color: #333333;
+ background-color: #eee;
+ text-align: left;
+}
+
+td.DevDetails li a {
+ padding-right: 5px;
+}
+
+td.DevComment {
+ font-weight: normal;
+ padding: 8px 8px 8px 8px;
+ color: #333333;
+ border-bottom-right-radius: 5px;
+ -webkit-border-bottom-right-radius: 5px;
+ -moz-border-radius-bottomright: 5px;
+ border-bottom-left-radius: 5px;
+ -webkit-border-bottom-left-radius: 5px;
+ -moz-border-radius-bottomleft: 5px;
+ background-color: #eee;
+ text-align: left;
+}
+
+td.Alt {
+ background-color: #ddd;
+}
+
+.legend {
+ border-radius: 5px;
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ width: 100px;
+ max-width: 100px;
+ text-align: center;
+ padding: 2px 2px 2px 2px;
+ height: 14px;
+ white-space: nowrap;
+}
+
+.DevStatusBox {
+ text-align: center;
+ height: 20px;
+ padding: 0 2px;
+ line-height: 0;
+ white-space: nowrap;
+}
+
+.DevStatusBox a {
+ opacity: 0.85;
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 4px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ display: block;
+ width: 90%;
+ height: 20px;
+ line-height: 20px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.DevSlaveBox {
+ text-align: center;
+ height: 10px;
+ padding: 0 2px;
+ line-height: 0;
+ white-space: nowrap;
+}
+
+.DevSlaveBox a {
+ opacity: 0.85;
+ border-width: 1px;
+ border-style: solid;
+ border-radius: 4px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ display: block;
+ width: 90%;
+ height: 10px;
+ line-height: 20px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+a.noround {
+ border-radius: 0px;
+ -webkit-border-radius: 0px;
+ -moz-border-radius: 0px;
+ position: relative;
+ margin-top: -8px;
+ margin-bottom: -8px;
+ height: 36px;
+ border-top-width: 0;
+ border-bottom-width: 0;
+}
+
+a.begin {
+ border-top-width: 1px;
+ position: relative;
+ margin-top: 0px;
+ margin-bottom: -7px;
+ height: 27px;
+ border-top-left-radius: 4px;
+ -webkit-border-top-left-radius: 4px;
+ -moz-border-radius-topleft: 4px;
+ border-top-right-radius: 4px;
+ -webkit-border-top-right-radius: 4px;
+ -moz-border-radius-topright: 4px;
+}
+
+a.end {
+ border-bottom-width: 1px;
+ position: relative;
+ margin-top: -7px;
+ margin-bottom: 0px;
+ height: 27px;
+ border-bottom-left-radius: 4px;
+ -webkit-border-bottom-left-radius: 4px;
+ -moz-border-radius-bottomleft: 4px;
+ border-bottom-right-radius: 4px;
+ -webkit-border-bottom-right-radius: 4px;
+ -moz-border-radius-bottomright: 4px;
+}
+
+.center_align {
+ text-align: center;
+}
+
+.right_align {
+ text-align: right;
+}
+
+.left_align {
+ text-align: left;
+}
+
+div.BuildWaterfall {
+ border-radius: 7px;
+ -webkit-border-radius: 7px;
+ -moz-border-radius: 7px;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ background-color: #FFFFFF;
+ padding: 4px 4px 4px 4px;
+ float: left;
+ display: none;
+ border-width: 1px;
+ border-style: solid;
+}
+
+/* LastBuild, BuildStep states */
+.success {
+ color: #000;
+ background-color: #8d4;
+ border-color: #4F8530;
+}
+
+.failure {
+ color: #000;
+ background-color: #e88;
+ border-color: #A77272;
+}
+
+.warnings {
+ color: #FFFFFF;
+ background-color: #fa3;
+ border-color: #C29D46;
+}
+
+.skipped {
+ color: #000;
+ background: #AADDEE;
+ border-color: #AADDEE;
+}
+
+.exception,.retry {
+ color: #FFFFFF;
+ background-color: #c6c;
+ border-color: #ACA0B3;
+}
+
+.start {
+ color: #000;
+ background-color: #ccc;
+ border-color: #ccc;
+}
+
+.running,.waiting,td.building {
+ color: #000;
+ background-color: #fd3;
+ border-color: #C5C56D;
+}
+
+.offline,td.offline {
+ color: #FFFFFF;
+ background-color: #777777;
+ border-color: #dddddd;
+}
+
+
+.start {
+ border-bottom-left-radius: 10px;
+ -webkit-border-bottom-left-radius: 10px;
+ -moz-border-radius-bottomleft: 10px;
+ border-bottom-right-radius: 10px;
+ -webkit-border-bottom-right-radius: 10px;
+ -moz-border-radius-bottomright: 10px;
+}
+
+.notstarted {
+ border-width: 1px;
+ border-style: solid;
+ border-color: #aaa;
+ background-color: #fff;
+}
+
+.closed {
+ background-color: #ff0000;
+}
+
+.closed .large {
+ font-size: 1.5em;
+ font-weight: bolder;
+}
+
+td.Project a:hover,td.start a:hover {
+ color: #000;
+}
+
+.mini-box {
+ text-align: center;
+ height: 20px;
+ padding: 0 2px;
+ line-height: 0;
+ white-space: nowrap;
+}
+
+.mini-box a {
+ border-radius: 0;
+ -webkit-border-radius: 0;
+ -moz-border-radius: 0;
+ display: block;
+ width: 100%;
+ height: 20px;
+ line-height: 20px;
+ margin-top: -30px;
+}
+
+.mini-closed {
+ -box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ border: 4px solid red;
+}
+
+/* grid styles */
+table.Grid {
+ border-collapse: collapse;
+}
+
+table.Grid tr td {
+ padding: 0.2em;
+ margin: 0px;
+ text-align: center;
+}
+
+table.Grid tr td.title {
+ font-size: 90%;
+ border-right: 1px gray solid;
+ border-bottom: 1px gray solid;
+}
+
+table.Grid tr td.sourcestamp {
+ font-size: 90%;
+}
+
+table.Grid tr td.builder {
+ text-align: right;
+ font-size: 90%;
+}
+
+table.Grid tr td.build {
+ border: 1px gray solid;
+}
+
+/* column container */
+div.column {
+ margin: 0 2em 2em 0;
+ float: left;
+}
+
+/* info tables */
+table.info {
+ border-spacing: 1px;
+}
+
+table.info td {
+ padding: 0.1em 1em 0.1em 1em;
+ text-align: center;
+}
+
+table.info th {
+ padding: 0.2em 1.5em 0.2em 1.5em;
+ text-align: center;
+}
+
+table.info td.left {
+ text-align: left
+}
+
+.alt {
+ background-color: #f6f6f6;
+}
+
+li {
+ padding: 0.1em 1em 0.1em 1em;
+}
+
+.result {
+ padding: 0.3em 1em 0.3em 1em;
+}
+
+/* log view */
+.log * {
+ vlink: #800080;
+ font-family: "Courier New", courier, monotype, monospace;
+}
+
+span.stdout {
+ color: black;
+}
+
+span.stderr {
+ color: red;
+}
+
+span.header {
+ color: blue;
+}
+
+/* revision & email */
+.revision .full {
+ display: none;
+}
+
+.user .email {
+ display: none;
+}
+
+/* change comments (use regular colors here) */
+pre.comments>a:link,pre.comments>a:visited {
+ color: blue;
+}
+
+pre.comments>a:active {
+ color: purple;
+}
diff --git a/master/templates/build.html b/master/templates/build.html
new file mode 100644
--- /dev/null
+++ b/master/templates/build.html
@@ -0,0 +1,204 @@
+{% extends "layout.html" %}
+{% import 'forms.html' as forms %}
+{% from "change_macros.html" import change with context %}
+
+{% block content %}
+
+<h1>
+Builder <a href="{{ path_to_builder }}">{{ b.getBuilder().getName() }}</a>
+Build #{{ b.getNumber() }}
+<!-- PyPy specific change: add a "view in summary" linke -->
+ (<a href="{{ path_to_root }}summary?builder={{
b.getBuilder().getName() }}">view in summary</a>)
+</h1>
+
+<div class="column">
+
+{% if not b.isFinished() %}
+ <h2>Build In Progress:</h2>
+
+ {% if when_time %}
+ <p>ETA: {{ when_time }} [{{ when }}]</p>
+ {% endif %}
+
+ {{ current_step }}
+
+ {% if authz.advertiseAction('stopBuild') %}
+ <h2>Stop Build</h2>
+ {{ forms.stop_build(build_url+"/stop", authz, on_all=False, short=False,
label='This Build') }}
+ {% endif %}
+{% else %}
+ <h2>Results:</h2>
+
+ <p class="{{ result_css }} result">
+ {{ b.getText()|join(' ')|capitalize }}
+ </p>
+
+ {% if b.getTestResults() %}
+ <h3><a href="{{ tests_link }}"/></h3>
+ {% endif %}
+{% endif %}
+
+
+<h2>SourceStamp:</h2>
+
+<table class="info" width="100%">
+{% set ss_class = cycler('alt','') %}
+
+{% if ss.project %}
+ <tr class="{{ ss_class.next() }}"><td class="left">Project</td><td>{{
ss.project|projectlink }}</td></tr>
+{% endif %}
+
+{% if ss.repository %}
+ <tr class="{{ ss_class.next() }}"><td class="left">Repository</td><td>{{
ss.repository|repolink }}</td></tr>
+{% endif %}
+
+{% if ss.branch %}
+ <tr class="{{ ss_class.next() }}"><td class="left">Branch</td><td>{{
ss.branch|e }}</td></tr>
+{% endif %}
+
+{% if ss.revision %}
+ <tr class="{{ ss_class.next() }}"><td class="left">Revision</td><td>{{
ss.revision|revlink(ss.repository) }}</td></tr>
+{% endif %}
+
+{% if got_revision %}
+ <tr class="{{ ss_class.next() }}"><td class="left">Got Revision</td><td>{{
got_revision|revlink(ss.repository) }}</td></tr>
+{% endif %}
+
+{% if ss.patch %}
+ <tr class="{{ ss_class.next() }}"><td
class="left">Patch</td><td>YES</td></tr>
+{% endif %}
+
+{% if ss.changes %}
+ <tr class="{{ ss_class.next() }}"><td class="left">Changes</td><td>see
below</td></tr>
+{% endif %}
+
+{% if most_recent_rev_build %}
+ <tr class="{{ ss_class.next() }}"><td class="left" colspan="2">Build of most
recent revision</td></tr>
+{% endif %}
+
+</table>
+
+{#
+ # TODO: turn this into a table, or some other sort of definition-list
+ # that doesn't take up quite so much vertical space
+ #}
+
+<h2>BuildSlave:</h2>
+
+{% if slave_url %}
+ <a href="{{ slave_url|e }}">{{ b.getSlavename()|e }}</a>
+{% else %}
+ {{ b.getSlavename()|e }}
+{% endif %}
+
+<h2>Reason:</h2>
+<p>
+{{ b.getReason()|e }}
+</p>
+
+<h2>Steps and Logfiles:</h2>
+
+{#
+ # TODO:
+ # urls = self.original.getURLs()
+ # ex_url_class = "BuildStep external"
+ # for name, target in urls.items():
+ # text.append('[<a href="%s" class="%s">%s</a>]' %
+ # (target, ex_url_class, html.escape(name)))
+ #}
+
+<ol>
+{% for s in steps %}
+ <li>
+ <div class="{{ s.css_class }} result">
+ <a href="{{ s.link }}">{{ s.name }}</a>
+ {{ s.text }} <span style="float:right">{{ '( ' + s.time_to_run + '
)' if s.time_to_run else '' }}</span>
+ </div>
+
+ <ol>
+ {% set item_class = cycler('alt', '') %}
+ {% for l in s.logs %}
+ <li class="{{ item_class.next() }}"><a href="{{ l.link }}">{{ l.name
}}</a></li>
+ {% else %}
+ <li class="{{ item_class.next() }}">- no logs -</li>
+ {% endfor %}
+
+ {% for u in s.urls %}
+ <li class="{{ item_class.next() }}"><a href="{{ u.url }}">{{ u.logname
}}</a></li>
+ {% endfor %}
+ </ol>
+ </li>
+{% endfor %}
+</ol>
+
+</div>
+<div class="column">
+
+<h2>Build Properties:</h2>
+
+<table class="info" width="100%">
+<tr><th>Name</th><th>Value</th><th>Source</th></tr>
+
+{% for p in properties %}
+ <tr class="{{ loop.cycle('alt', '') }}">
+ <td class="left">{{ p.name|e }}</td>
+ {% if p.short_value %}
+ <td>{{ p.short_value|e }} .. [property value too long]</td>
+ {% else %}
+ <td>{{ p.value|e }}</td>
+ {% endif %}
+ <td>{{ p.source|e }}</td>
+ </tr>
+{% endfor %}
+
+</table>
+
+<h2>Blamelist:</h2>
+
+{% if responsible_users %}
+ <ol>
+ {% for u in responsible_users %}
+ <li class="{{ loop.cycle('alt', '') }}">{{ u|user }}</li>
+ {% endfor %}
+ </ol>
+{% else %}
+ <p>no responsible users</p>
+{% endif %}
+
+
+<h2>Timing:</h2>
+<table class="info" width="100%">
+ <tr class="alt"><td class="left">Start</td><td>{{ start }}</td></tr>
+{% if end %}
+ <tr><td class="left">End</td><td>{{ end }}</td></tr>
+{% endif %}
+ <tr {{ 'class="alt"' if end else '' }}><td class="left">Elapsed</td><td>{{
elapsed }}</td></tr>
+</table>
+
+<!-- PyPy specific change: hide the "resubmit build section"
+ {% if authz.advertiseAction('forceBuild') %}
+ <h3>Resubmit Build:</h3>
+ {{ forms.rebuild_build(build_url+"/rebuild", authz, exactly, ss) }}
+ {% endif %}
+-->
+
+</div>
+
+<br style="clear:both"/>
+
+{% if ss.changes %}
+<div class="column">
+ <h2>All Changes:</h2>
+ <ol>
+ {% for c in ss.changes %}
+ <li><h3>Change #{{ c.number }}</h3>
+ {{ change(c.asDict()) }}
+ </li>
+ {% else %}
+ <li>no changes</li>
+ {% endfor %}
+ </ol>
+</div>
+{% endif %}
+
+{% endblock %}
diff --git a/master/templates/builder.html b/master/templates/builder.html
new file mode 100644
--- /dev/null
+++ b/master/templates/builder.html
@@ -0,0 +1,113 @@
+{% from 'build_line.html' import build_table %}
+{% import 'forms.html' as forms %}
+
+{% extends "layout.html" %}
+{% block content %}
+
+<h1>Builder {{ name }}</h1>
+
+<!-- PyPy specific change: add the "view in summary" link -->
+<p>
+ (<a href="{{ path_to_root }}waterfall?show={{ name }}">view in waterfall</a>)
+
+ (<a href="{{ path_to_root }}summary?builder={{ name }}">view in summary</a>)
+</p>
+
+<div class="column">
+
+{% if current %}
+ <h2>Current Builds:</h2>
+ <ul>
+ {% for b in current %}
+ <li><a href="{{ b.link }}">{{ b.num }}</a>
+ {% if b.when %}
+ ETA: {{ b.when_time }} [{{ b.when }}]
+ {% endif %}
+
+ {{ b.current_step }}
+
+ {% if authz.advertiseAction('stopBuild') %}
+ {{ forms.stop_build(b.stop_url, authz, on_all=False, short=True,
label='Build') }}
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <h2>No current builds</h2>
+{% endif %}
+
+{% if pending %}
+ <h2>Pending Build Requests:</h2>
+ <ul>
+ {% for b in pending %}
+ <li><small>({{ b.when }}, waiting {{ b.delay }})</small>
+
+ {% if authz.advertiseAction('cancelPendingBuild') %}
+ {{ forms.cancel_pending_build(builder_url+"/cancelbuild", authz,
short=True, id=b.id) }}
+ {% endif %}
+
+ {% if b.num_changes < 4 %}
+ {% for c in b.changes %}{{ c.revision|shortrev(c.repo) }}
+ (<a href="{{ c.url }}">{{ c.who }}</a>){% if not loop.last %},{% endif
%}
+ {% endfor %}
+ {% else %}
+ ({{ b.num_changes }} changes)
+ {% endif %}
+
+ </li>
+ {% endfor %}
+ </ul>
+
+ {% if authz.advertiseAction('cancelPendingBuild') %}
+ {{ forms.cancel_pending_build(builder_url+"/cancelbuild", authz,
short=False, id='all') }}
+ {% endif %}
+
+{% else %}
+ <h2>No Pending Build Requests</h2>
+{% endif %}
+
+<h2>Recent Builds:</h2>
+
+{{ build_table(recent) }}
+
+</div>
+<div class="column">
+
+<h2>Buildslaves:</h2>
+<table class="info">
+{% if slaves %}
+<tr>
+ <th>Name</th>
+ <th>Status</th>
+ <th>Admin</th>
+</tr>
+{% endif %}
+{% for s in slaves %}
+ <tr class="{{ loop.cycle('alt', '') }}">
+ <td><b><a href="{{ s.link|e }}">{{ s.name|e }}</a></b></td>
+ {% if s.connected %}
+ <td class="idle">connected</td>
+ <td>{{ s.admin|email if s.admin else ""}}</td>
+ {% else %}
+ <td class="offline">offline</td>
+ <td/>
+ {% endif %}
+ </tr>
+{% else %}
+ <td>no slaves attached</td>
+{% endfor %}
+</table>
+
+{% if authz.advertiseAction('pingBuilder') %}
+ <h2>Ping slaves</h2>
+ {{ forms.ping_builder(builder_url+"/ping", authz) }}
+{% endif %}
+
+{% if authz.advertiseAction('forceBuild') %}
+ <h2>Force build</h2>
+ {{ forms.force_build(builder_url+"/force", authz, False) }}
+{% endif %}
+
+</div>
+
+{% endblock %}
diff --git a/master/templates/layout.html b/master/templates/layout.html
new file mode 100644
--- /dev/null
+++ b/master/templates/layout.html
@@ -0,0 +1,73 @@
+{%- block doctype -%}
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+{% endblock %}
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ {% block head %}
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ {% if metatags %}
+ {{ metatags }}
+ {% endif %}
+ {% if refresh %}
+ <meta http-equiv="refresh" content="{{ refresh|e }}"/>
+ {% endif %}
+ <title>{{ pageTitle|e }}</title>
+ <link rel="stylesheet" href="{{ stylesheet }}" type="text/css" />
+ <link rel="alternate" type="application/rss+xml" title="RSS" href="{{
path_to_root }}rss">
+ {% block morehead %}{% endblock %}
+ {% endblock %}
+ </head>
+ <body class="interface">
+ {% block header -%}
+ <div class="header">
+ <a href="{{ path_to_root or '.' }}">Home</a>
+ -
+ <!-- PyPy specific items -->
+ <a href="http://speed.pypy.org/">Speed</a>
+ <a href="{{ path_to_root }}summary?branch=<trunk>">Summary
(trunk)</a>
+ <a href="{{ path_to_root }}summary">Summary</a>
+ <a href="{{ path_to_root }}nightly/">Nightly builds</a>
+ <!-- end of PyPy specific items -->
+
+ <a href="{{ path_to_root }}waterfall">Waterfall</a>
+ <!-- <a href="{{ path_to_root }}grid">Grid</a> -->
+ <!-- <a href="{{ path_to_root }}tgrid">T-Grid</a> -->
+ <!-- <a href="{{ path_to_root }}console">Console</a> -->
+ <a href="{{ path_to_root }}builders">Builders</a>
+ <!-- <a href="{{ path_to_root }}one_line_per_build">Recent Builds</a>
-->
+ <!-- <a href="{{ path_to_root }}buildslaves">Buildslaves</a> -->
+ <!-- <a href="{{ path_to_root }}changes">Changesources</a> -->
+ <!-- - <a href="{{ path_to_root }}json/help">JSON API</a> -->
+ - <a href="{{ path_to_root }}about">About</a>
+ </div>
+ {% endblock %}
+
+ {%- block barecontent -%}
+ <hr/>
+
+ <div class="content">
+ {%- block content -%}
+ {%- endblock -%}
+ </div>
+ {%- endblock -%}
+
+ {%- block footer -%}
+ <div class="footer" style="clear:both">
+ <hr/>
+ <a href="http://buildbot.net/">BuildBot</a> ({{version}})
+ {% if title -%}
+ working for the
+ {%- if title_url -%}
+ <a href="{{ title_url }}">{{ title }}</a>
+ {%- else -%}
+ {{ title }}
+ {%- endif -%}
+ project.
+ {%- endif -%}
+ <br/>
+ Page built: <b>{{ time }}</b> ({{ tz }})
+ </div>
+ {% endblock -%}
+ </body>
+</html>
diff --git a/master/templates/root.html b/master/templates/root.html
new file mode 100644
--- /dev/null
+++ b/master/templates/root.html
@@ -0,0 +1,87 @@
+{% extends 'layout.html' %}
+{% import 'forms.html' as forms %}
+
+{% block content %}
+
+<h1>Welcome to the Buildbot
+{%- if title -%}
+ for the
+ {%- if title_url -%}
+ <a href="{{ title_url }}">{{ title }}</a>
+ {%- else -%}
+ {{ title }}
+ {%- endif -%}
+ project
+{%- endif -%}
+!
+</h1>
+
+<div class="column">
+
+<ul>
+ {% set item_class=cycler('alt', '') %}
+
+ <!-- PyPy specific items -->
+ <li class="{{ item_class.next() }}">
+ The <a href="http://speed.pypy.org/">Performance Plots</a> will give you
+ an overview of performance for recent revisions.
+ </li>
+
+ <li class="{{ item_class.next() }}">
+ The <a href="summary?branch=<trunk>">Summary Display
+ <trunk></a> will give you a failure-oriented summary for recent
+ revisions (<trunk> only).
+ </li>
+
+ <li class="{{ item_class.next() }}">
+ The <a href="summary">Summary Display</a> will give you a failure-oriented
+ summary for recent revisions (all branches).
+ </li>
+
+ <li class="{{ item_class.next() }}">
+ The <a href="nightly/">Nightly Build</a> page contains precompiled PyPy
+ binaries.
+ </li>
+ <!-- end of PyPy specific items -->
+
+ <li class="{{ item_class.next() }}">The <a href="waterfall">Waterfall
Display</a> will give you a
+ time-oriented summary of recent buildbot activity. <a
href="waterfall/help">Waterfall Help.</a></li>
+
+ <li class="{{ item_class.next() }}">The <a href="grid">Grid Display</a> will
give you a
+ developer-oriented summary of recent buildbot activity.</li>
+
+ <li class="{{ item_class.next() }}">The <a href="tgrid">Transposed Grid
Display</a> presents
+ the same information as the grid, but lists the revisions down the side.</li>
+
+ <li class="{{ item_class.next() }}">The <a href="console">Console</a>
presents
+ a user-oriented status page.</li>
+
+ <li class="{{ item_class.next() }}">The <a href="builders">Builders</a> and
their most recent builds are
+ here.</li>
+
+ <li class="{{ item_class.next() }}"><a href="one_line_per_build">Recent
Builds</a> are summarized here, one
+ per line.</li>
+
+ <li class="{{ item_class.next() }}"><a href="buildslaves">Buildslave</a>
information</li>
+ <li class="{{ item_class.next() }}"><a href="changes">Changesource</a>
information.</li>
+
+ <li class="{{ item_class.next() }}"><a href="about">About</a> this
Buildbot</li>
+</ul>
+
+<!-- PyPy specific: comment out the clean shutdown button
+{%- if authz.advertiseAction('cleanShutdown') -%}
+{%- if shutting_down -%}
+Master is shutting down<br/>
+{{ forms.cancel_clean_shutdown(cancel_shutdown_url, authz) }}
+{%- else -%}
+{{ forms.clean_shutdown(shutdown_url, authz) }}
+{%- endif -%}
+{%- endif -%}
+
+<p><i>This and other pages can be overridden and customized.</i></p>
+
+-->
+
+</div>
+
+{% endblock %}
diff --git a/master/templates/summary.html b/master/templates/summary.html
new file mode 100644
--- /dev/null
+++ b/master/templates/summary.html
@@ -0,0 +1,43 @@
+{% extends "layout.html" %}
+
+{% block morehead %}
+<link rel="stylesheet" href="{{ path_to_root }}summary.css" type="text/css" />'
+<script language=javascript type='text/javascript'>
+ hiddenstates = [ ];
+ function togglestate(a, c) {
+ var start = "a" + a + "c";
+ var link = document.getElementById(start + c);
+ var state = hiddenstates[a];
+ if (!state) state = 0;
+ if (state & c) {
+ state = state - c;
+ link.textContent = '-';
+ }
+ else {
+ state = state | c;
+ link.textContent = 'H';
+ }
+ hiddenstates[a] = state;
+ var items = document.getElementsByTagName('span');
+ var i = items.length;
+ var toggle = "";
+ while (i > 0) {
+ i--;
+ var span = items.item(i);
+ if (span.className.substr(0, start.length) == start) {
+ var k = span.className.substr(start.length);
+ if ((state & k) == k)
+ span.style.display = 'none';
+ else
+ span.style.display = 'block';
+ }
+ }
+ }
+</script>
+{% endblock %}
+
+
+
+{% block content %}
+{{ content }}
+{% endblock %}
diff --git a/slave/buildbot.tac b/slave/buildbot.tac
--- a/slave/buildbot.tac
+++ b/slave/buildbot.tac
@@ -1,6 +1,11 @@
# -*- mode: python -*-
from twisted.application import service
-from buildbot.slave.bot import BuildSlave
+try:
+ # 8.x
+ from buildslave.bot import BuildSlave
+except ImportError:
+ #7.x
+ from buildbot.slave.bot import BuildSlave
# ---------------------------------------------------------------
# manual editing of the automatically generated buildbot.tac
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit