Package buildbot :: Package status :: Package web :: Module build
[hide private]
[frames] | no frames]

Source Code for Module buildbot.status.web.build

  1   
  2  from twisted.web import html 
  3  from twisted.web.util import Redirect, DeferredResource 
  4  from twisted.internet import defer, reactor 
  5   
  6  import urllib, time 
  7  from twisted.python import log 
  8  from buildbot.status.web.base import HtmlResource, make_row, make_stop_form, \ 
  9       css_classes, path_to_builder, path_to_slave, make_name_user_passwd_form 
 10   
 11  from buildbot.status.web.tests import TestsResource 
 12  from buildbot.status.web.step import StepsResource 
 13  from buildbot import version, util 
 14   
 15  # /builders/$builder/builds/$buildnum 
16 -class StatusResourceBuild(HtmlResource):
17 addSlash = True 18
19 - def __init__(self, build_status, build_control, builder_control):
20 HtmlResource.__init__(self) 21 self.build_status = build_status 22 self.build_control = build_control 23 self.builder_control = builder_control
24
25 - def getTitle(self, request):
26 return ("Buildbot: %s Build #%d" % 27 (html.escape(self.build_status.getBuilder().getName()), 28 self.build_status.getNumber()))
29
30 - def body(self, req):
31 b = self.build_status 32 status = self.getStatus(req) 33 projectURL = status.getProjectURL() 34 projectName = status.getProjectName() 35 data = ('<div class="title"><a href="%s">%s</a></div>\n' 36 % (self.path_to_root(req), projectName)) 37 builder_name = b.getBuilder().getName() 38 data += ("<h1><a href=\"%s\">Builder %s</a>: Build #%d</h1>\n" 39 % (path_to_builder(req, b.getBuilder()), 40 builder_name, b.getNumber())) 41 42 if not b.isFinished(): 43 data += "<h2>Build In Progress</h2>" 44 when = b.getETA() 45 if when is not None: 46 when_time = time.strftime("%H:%M:%S", 47 time.localtime(time.time() + when)) 48 data += "<div>ETA %ds (%s)</div>\n" % (when, when_time) 49 50 if self.build_control is not None: 51 stopURL = urllib.quote(req.childLink("stop")) 52 data += make_stop_form(stopURL, self.isUsingUserPasswd(req)) 53 54 if b.isFinished(): 55 # Results map loosely to css_classes 56 results = b.getResults() 57 data += "<h2>Results:</h2>\n" 58 text = " ".join(b.getText()) 59 data += '<span class="%s">%s</span>\n' % (css_classes[results], 60 text) 61 if b.getTestResults(): 62 url = req.childLink("tests") 63 data += "<h3><a href=\"%s\">test results</a></h3>\n" % url 64 65 ss = b.getSourceStamp() 66 data += "<h2>SourceStamp:</h2>\n" 67 data += " <ul>\n" 68 if ss.branch: 69 data += " <li>Branch: %s</li>\n" % html.escape(ss.branch) 70 if ss.revision: 71 data += " <li>Revision: %s</li>\n" % html.escape(str(ss.revision)) 72 if ss.patch: 73 data += " <li>Patch: YES</li>\n" # TODO: provide link to .diff 74 if ss.changes: 75 data += " <li>Changes: see below</li>\n" 76 if (ss.branch is None and ss.revision is None and ss.patch is None 77 and not ss.changes): 78 data += " <li>build of most recent revision</li>\n" 79 got_revision = None 80 try: 81 got_revision = b.getProperty("got_revision") 82 except KeyError: 83 pass 84 if got_revision: 85 got_revision = str(got_revision) 86 if len(got_revision) > 40: 87 got_revision = "[revision string too long]" 88 data += " <li>Got Revision: %s</li>\n" % got_revision 89 data += " </ul>\n" 90 91 # TODO: turn this into a table, or some other sort of definition-list 92 # that doesn't take up quite so much vertical space 93 try: 94 slaveurl = path_to_slave(req, status.getSlave(b.getSlavename())) 95 data += "<h2>Buildslave:</h2>\n <a href=\"%s\">%s</a>\n" % (html.escape(slaveurl), html.escape(b.getSlavename())) 96 except KeyError: 97 data += "<h2>Buildslave:</h2>\n %s\n" % html.escape(b.getSlavename()) 98 data += "<h2>Reason:</h2>\n%s\n" % html.escape(b.getReason()) 99 100 data += "<h2>Steps and Logfiles:</h2>\n" 101 # TODO: 102 # urls = self.original.getURLs() 103 # ex_url_class = "BuildStep external" 104 # for name, target in urls.items(): 105 # text.append('[<a href="%s" class="%s">%s</a>]' % 106 # (target, ex_url_class, html.escape(name))) 107 data += "<ol>\n" 108 for s in b.getSteps(): 109 name = s.getName() 110 time_to_run = 0 111 (start, end) = s.getTimes() 112 if start and end: 113 time_to_run = end - start 114 if s.isFinished(): 115 css_class = css_classes[s.getResults()[0]] 116 elif s.isStarted(): 117 css_class = "running" 118 else: 119 css_class = "" 120 data += (' <li><span class="%s"><a href=\"%s\">%s</a> [%s] [%d seconds]</span>\n' 121 % (css_class, 122 req.childLink("steps/%s" % urllib.quote(name)), 123 name, 124 " ".join(s.getText()), 125 time_to_run)) 126 if s.getLogs(): 127 data += " <ol>\n" 128 for logfile in s.getLogs(): 129 logname = logfile.getName() 130 logurl = req.childLink("steps/%s/logs/%s" % 131 (urllib.quote(name), 132 urllib.quote(logname))) 133 data += (" <li><a href=\"%s\">%s</a></li>\n" % 134 (logurl, logfile.getName())) 135 data += " </ol>\n" 136 data += " </li>\n" 137 data += "</ol>\n" 138 139 data += "<h2>Build Properties:</h2>\n" 140 data += "<table><tr><th valign=\"left\">Name</th><th valign=\"left\">Value</th><th valign=\"left\">Source</th></tr>\n" 141 for name, value, source in b.getProperties().asList(): 142 value = str(value) 143 if len(value) > 500: 144 value = value[:500] + " .. [property value too long]" 145 data += "<tr>" 146 data += "<td>%s</td>" % html.escape(name) 147 data += "<td>%s</td>" % html.escape(value) 148 data += "<td>%s</td>" % html.escape(source) 149 data += "</tr>\n" 150 data += "</table>" 151 152 data += "<h2>Blamelist:</h2>\n" 153 if list(b.getResponsibleUsers()): 154 data += " <ol>\n" 155 for who in b.getResponsibleUsers(): 156 data += " <li>%s</li>\n" % html.escape(who) 157 data += " </ol>\n" 158 else: 159 data += "<div>no responsible users</div>\n" 160 161 162 (start, end) = b.getTimes() 163 data += "<h2>Timing</h2>\n" 164 data += "<table>\n" 165 data += "<tr><td>Start</td><td>%s</td></tr>\n" % time.ctime(start) 166 if end: 167 data += "<tr><td>End</td><td>%s</td></tr>\n" % time.ctime(end) 168 data += "<tr><td>Elapsed</td><td>%s</td></tr>\n" % util.formatInterval(end - start) 169 else: 170 now = util.now() 171 data += "<tr><td>Elapsed</td><td>%s</td></tr>\n" % util.formatInterval(now - start) 172 data += "</table>\n" 173 174 if ss.changes: 175 data += "<h2>All Changes</h2>\n" 176 data += "<ol>\n" 177 for c in ss.changes: 178 data += "<li>" + c.asHTML() + "</li>\n" 179 data += "</ol>\n" 180 #data += html.PRE(b.changesText()) # TODO 181 182 if b.isFinished() and self.builder_control is not None: 183 data += "<h3>Resubmit Build:</h3>\n" 184 # can we rebuild it exactly? 185 exactly = (ss.revision is not None) or b.getChanges() 186 if exactly: 187 data += ("<p>This tree was built from a specific set of \n" 188 "source files, and can be rebuilt exactly</p>\n") 189 else: 190 data += ("<p>This tree was built from the most recent " 191 "revision") 192 if ss.branch: 193 data += " (along some branch)" 194 data += (" and thus it might not be possible to rebuild it \n" 195 "exactly. Any changes that have been committed \n" 196 "after this build was started <b>will</b> be \n" 197 "included in a rebuild.</p>\n") 198 rebuildURL = urllib.quote(req.childLink("rebuild")) 199 data += ('<form method="post" action="%s" class="command rebuild">\n' 200 % rebuildURL) 201 data += make_name_user_passwd_form(self.isUsingUserPasswd(req)) 202 data += make_row("Reason for re-running build:", 203 "<input type='text' name='comments' />") 204 data += '<input type="submit" value="Rebuild" />\n' 205 data += '</form>\n' 206 207 # TODO: this stuff should be generated by a template of some sort 208 data += '<hr /><div class="footer">\n' 209 210 welcomeurl = self.path_to_root(req) + "index.html" 211 data += '[<a href="%s">welcome</a>]\n' % welcomeurl 212 data += "<br />\n" 213 214 data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>' 215 data += "-%s " % version 216 if projectName: 217 data += "working for the " 218 if projectURL: 219 data += "<a href=\"%s\">%s</a> project." % (projectURL, 220 projectName) 221 else: 222 data += "%s project." % projectName 223 data += "<br />\n" 224 data += ("Page built: " + 225 time.strftime("%a %d %b %Y %H:%M:%S", 226 time.localtime(util.now())) 227 + "\n") 228 data += '</div>\n' 229 230 return data
231
232 - def stop(self, req):
233 if self.isUsingUserPasswd(req): 234 if not self.authUser(req): 235 return Redirect("../../../authfailed") 236 b = self.build_status 237 c = self.build_control 238 log.msg("web stopBuild of build %s:%s" % \ 239 (b.getBuilder().getName(), b.getNumber())) 240 name = req.args.get("username", ["<unknown>"])[0] 241 comments = req.args.get("comments", ["<no reason specified>"])[0] 242 reason = ("The web-page 'stop build' button was pressed by " 243 "'%s': %s\n" % (name, comments)) 244 if c: 245 c.stopBuild(reason) 246 # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and 247 # we want to go to: http://localhost:8080/svn-hello 248 url = req.args.get('url', ['../..'])[0] 249 r = Redirect(url) 250 d = defer.Deferred() 251 reactor.callLater(1, d.callback, r) 252 return DeferredResource(d)
253
254 - def rebuild(self, req):
255 if self.isUsingUserPasswd(req): 256 if not self.authUser(req): 257 return Redirect("../../../authfailed") 258 b = self.build_status 259 bc = self.builder_control 260 builder_name = b.getBuilder().getName() 261 log.msg("web rebuild of build %s:%s" % (builder_name, b.getNumber())) 262 name = req.args.get("username", ["<unknown>"])[0] 263 comments = req.args.get("comments", ["<no reason specified>"])[0] 264 reason = ("The web-page 'rebuild' button was pressed by " 265 "'%s': %s\n" % (name, comments)) 266 if not bc or not b.isFinished(): 267 log.msg("could not rebuild: bc=%s, isFinished=%s" 268 % (bc, b.isFinished())) 269 # TODO: indicate an error 270 else: 271 bc.resubmitBuild(b, reason) 272 # we're at 273 # http://localhost:8080/builders/NAME/builds/5/rebuild?[args] 274 # Where should we send them? 275 # 276 # Ideally it would be to the per-build page that they just started, 277 # but we don't know the build number for it yet (besides, it might 278 # have to wait for a current build to finish). The next-most 279 # preferred place is somewhere that the user can see tangible 280 # evidence of their build starting (or to see the reason that it 281 # didn't start). This should be the Builder page. 282 r = Redirect("../..") # the Builder's page 283 d = defer.Deferred() 284 reactor.callLater(1, d.callback, r) 285 return DeferredResource(d)
286
287 - def getChild(self, path, req):
288 if path == "stop": 289 return self.stop(req) 290 if path == "rebuild": 291 return self.rebuild(req) 292 if path == "steps": 293 return StepsResource(self.build_status) 294 if path == "tests": 295 return TestsResource(self.build_status) 296 297 return HtmlResource.getChild(self, path, req)
298 299 # /builders/$builder/builds
300 -class BuildsResource(HtmlResource):
301 addSlash = True 302
303 - def __init__(self, builder_status, builder_control):
304 HtmlResource.__init__(self) 305 self.builder_status = builder_status 306 self.builder_control = builder_control
307
308 - def getChild(self, path, req):
309 try: 310 num = int(path) 311 except ValueError: 312 num = None 313 if num is not None: 314 build_status = self.builder_status.getBuild(num) 315 if build_status: 316 if self.builder_control: 317 build_control = self.builder_control.getBuild(num) 318 else: 319 build_control = None 320 return StatusResourceBuild(build_status, build_control, 321 self.builder_control) 322 323 return HtmlResource.getChild(self, path, req)
324