[pypy-commit] buildbot default: merge heads

arigo noreply at buildbot.pypy.org
Wed Aug 24 11:34:37 CEST 2011


Author: Armin Rigo <arigo at tunes.org>
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&nbsp;&nbsp;&nbsp;(<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&nbsp;&nbsp;&nbsp;(<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 -->
+&nbsp;&nbsp;&nbsp;(<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 }}&nbsp;<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>)
+  &nbsp;&nbsp;&nbsp;
+  (<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=&lt;trunk&gt;">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&nbsp;
+        {%- if title_url -%}
+          <a href="{{ title_url }}">{{ title }}</a>
+        {%- else -%}
+          {{ title }}
+        {%- endif -%}
+        &nbsp;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 -%}
+  &nbsp;for the&nbsp;
+  {%- if title_url -%}
+    <a href="{{ title_url }}">{{ title }}</a>
+  {%- else -%}
+    {{ title }}
+  {%- endif -%}
+&nbsp;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=&lt;trunk&gt;">Summary Display
+    &lt;trunk&gt;</a> will give you a failure-oriented summary for recent
+    revisions (&lt;trunk&gt; 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


More information about the pypy-commit mailing list