| Index: third_party/buildbot_8_4p1/README.chromium
|
| diff --git a/third_party/buildbot_8_4p1/README.chromium b/third_party/buildbot_8_4p1/README.chromium
|
| index 9beb04ba651d6231c752cf63d272b40faef8065c..5af4ce9e639c78085028c54277a2b47041d42bc6 100644
|
| --- a/third_party/buildbot_8_4p1/README.chromium
|
| +++ b/third_party/buildbot_8_4p1/README.chromium
|
| @@ -4,5953 +4,8 @@ License: GNU General Public License (GPL) Version 2
|
|
|
| This is a forked copy of buildbot v0.8.4p1.
|
|
|
| -Make hidden steps stay hidden even if not finished, add brDoStepIf
|
| -to support buildrunner.
|
| +The fork starts at ece89304e7e6e7f06016de19cdb623e3c7137d2e.
|
|
|
| +To produce the current diff, run:
|
|
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -index 4aa307d..21044ea 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -@@ -621,6 +621,7 @@ class BuildStep:
|
| - 'progressMetrics',
|
| - 'doStepIf',
|
| - 'hideStepIf',
|
| -+ 'brDoStepIf',
|
| - ]
|
| -
|
| - name = "generic"
|
| -@@ -633,6 +634,9 @@ class BuildStep:
|
| - # doStepIf can be False, True, or a function that returns False or True
|
| - doStepIf = True
|
| - hideStepIf = False
|
| -+ # like doStepIf, but evaluated at runtime if executing under runbuild.py
|
| -+ # we also overload 'False' to signify this isn't a buildrunner step
|
| -+ brDoStepIf = False
|
| -
|
| - def __init__(self, **kwargs):
|
| - self.factory = (self.__class__, dict(kwargs))
|
| -@@ -679,6 +683,8 @@ class BuildStep:
|
| -
|
| - def setStepStatus(self, step_status):
|
| - self.step_status = step_status
|
| -+ hidden = self._maybeEvaluate(self.hideStepIf, self.step_status)
|
| -+ self.step_status.setHidden(hidden)
|
| -
|
| - def setupProgress(self):
|
| - if self.useProgress:
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -index 02e4b4d..c634060 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -@@ -95,10 +95,10 @@ class StatusResourceBuild(HtmlResource):
|
| - for s in b.getSteps():
|
| - step = {'name': s.getName() }
|
| -
|
| -- if s.isFinished():
|
| -- if s.isHidden():
|
| -- continue
|
| -+ if s.isHidden():
|
| -+ continue
|
| -
|
| -+ if s.isFinished():
|
| - step['css_class'] = css_classes[s.getResults()[0]]
|
| - (start, end) = s.getTimes()
|
| - step['time_to_run'] = util.formatInterval(end - start)
|
| -
|
| -
|
| -Apply 2577dac35b39c4ee9c419681021d7971e8f1e5d2 and
|
| -3db80d46d3c35d71591f033e5c3d0a2fa0001c59 from buildbot upstream.
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -index ea42207..4aa307d 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -@@ -620,6 +620,7 @@ class BuildStep:
|
| - 'alwaysRun',
|
| - 'progressMetrics',
|
| - 'doStepIf',
|
| -+ 'hideStepIf',
|
| - ]
|
| -
|
| - name = "generic"
|
| -@@ -631,6 +632,7 @@ class BuildStep:
|
| - progress = None
|
| - # doStepIf can be False, True, or a function that returns False or True
|
| - doStepIf = True
|
| -+ hideStepIf = False
|
| -
|
| - def __init__(self, **kwargs):
|
| - self.factory = (self.__class__, dict(kwargs))
|
| -@@ -920,6 +922,10 @@ class BuildStep:
|
| - if self.progress:
|
| - self.progress.finish()
|
| - self.step_status.stepFinished(results)
|
| -+
|
| -+ hidden = self._maybeEvaluate(self.hideStepIf, self.step_status)
|
| -+ self.step_status.setHidden(hidden)
|
| -+
|
| - self.releaseLocks()
|
| - self.deferred.callback(results)
|
| -
|
| -@@ -938,6 +944,9 @@ class BuildStep:
|
| - self.step_status.setText([self.name, "exception"])
|
| - self.step_status.setText2([self.name])
|
| - self.step_status.stepFinished(EXCEPTION)
|
| -+
|
| -+ hidden = self._maybeEvaluate(self.hideStepIf, EXCEPTION, self)
|
| -+ self.step_status.setHidden(hidden)
|
| - except:
|
| - log.msg("exception during failure processing")
|
| - log.err()
|
| -@@ -1048,6 +1057,11 @@ class BuildStep:
|
| - d = c.run(self, self.remote)
|
| - return d
|
| -
|
| -+ @staticmethod
|
| -+ def _maybeEvaluate(value, *args, **kwargs):
|
| -+ if callable(value):
|
| -+ value = value(*args, **kwargs)
|
| -+ return value
|
| -
|
| - class OutputProgressObserver(LogObserver):
|
| - length = 0
|
| -@@ -1294,4 +1308,3 @@ def regex_log_evaluator(cmd, step_status, regexes):
|
| - from buildbot.process.properties import WithProperties
|
| - _hush_pyflakes = [WithProperties]
|
| - del _hush_pyflakes
|
| --
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
|
| -index 781f7e8..264b599 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
|
| -@@ -46,7 +46,7 @@ class BuildStepStatus(styles.Versioned):
|
| - # corresponding BuildStep has started.
|
| - implements(interfaces.IBuildStepStatus, interfaces.IStatusEvent)
|
| -
|
| -- persistenceVersion = 3
|
| -+ persistenceVersion = 4
|
| - persistenceForgets = ( 'wasUpgraded', )
|
| -
|
| - started = None
|
| -@@ -60,6 +60,7 @@ class BuildStepStatus(styles.Versioned):
|
| - finishedWatchers = []
|
| - statistics = {}
|
| - step_number = None
|
| -+ hidden = False
|
| -
|
| - def __init__(self, parent, step_number):
|
| - assert interfaces.IBuildStatus(parent)
|
| -@@ -67,6 +68,7 @@ class BuildStepStatus(styles.Versioned):
|
| - self.build_number = parent.getNumber()
|
| - self.builder = parent.getBuilder()
|
| - self.step_number = step_number
|
| -+ self.hidden = False
|
| - self.logs = []
|
| - self.urls = {}
|
| - self.watchers = []
|
| -@@ -120,6 +122,9 @@ class BuildStepStatus(styles.Versioned):
|
| - def isFinished(self):
|
| - return (self.finished is not None)
|
| -
|
| -+ def isHidden(self):
|
| -+ return self.hidden
|
| -+
|
| - def waitUntilFinished(self):
|
| - if self.finished:
|
| - d = defer.succeed(self)
|
| -@@ -214,6 +219,9 @@ class BuildStepStatus(styles.Versioned):
|
| - def setProgress(self, stepprogress):
|
| - self.progress = stepprogress
|
| -
|
| -+ def setHidden(self, hidden):
|
| -+ self.hidden = hidden
|
| -+
|
| - def stepStarted(self):
|
| - self.started = util.now()
|
| - build = self.getBuild()
|
| -@@ -354,6 +362,11 @@ class BuildStepStatus(styles.Versioned):
|
| - self.step_number = 0
|
| - self.wasUpgraded = True
|
| -
|
| -+ def upgradeToVersion4(self):
|
| -+ if not hasattr(self, "hidden"):
|
| -+ self.hidden = False
|
| -+ self.wasUpgraded = True
|
| -+
|
| - def asDict(self):
|
| - result = {}
|
| - # Constant
|
| -@@ -370,6 +383,7 @@ class BuildStepStatus(styles.Versioned):
|
| - result['eta'] = self.getETA()
|
| - result['urls'] = self.getURLs()
|
| - result['step_number'] = self.step_number
|
| -+ result['hidden'] = self.hidden
|
| - result['logs'] = [[l.getName(),
|
| - self.builder.status.getURLForThing(l)]
|
| - for l in self.getLogs()]
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -index 907adc8..02e4b4d 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -@@ -94,9 +94,11 @@ class StatusResourceBuild(HtmlResource):
|
| -
|
| - for s in b.getSteps():
|
| - step = {'name': s.getName() }
|
| -- cxt['steps'].append(step)
|
| -
|
| - if s.isFinished():
|
| -+ if s.isHidden():
|
| -+ continue
|
| -+
|
| - step['css_class'] = css_classes[s.getResults()[0]]
|
| - (start, end) = s.getTimes()
|
| - step['time_to_run'] = util.formatInterval(end - start)
|
| -@@ -111,6 +113,8 @@ class StatusResourceBuild(HtmlResource):
|
| - step['css_class'] = "not_started"
|
| - step['time_to_run'] = ""
|
| -
|
| -+ cxt['steps'].append(step)
|
| -+
|
| - step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName()))
|
| - step['text'] = " ".join(s.getText())
|
| - step['urls'] = map(lambda x:dict(url=x[1],logname=x[0]), s.getURLs().items())
|
| -@@ -255,4 +259,3 @@ class BuildsResource(HtmlResource):
|
| - return StatusResourceBuild(build_status)
|
| -
|
| - return HtmlResource.getChild(self, path, req)
|
| --
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
|
| -index 2c04824..923fe0d 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
|
| -@@ -576,13 +576,19 @@ class WaterfallStatusResource(HtmlResource):
|
| - try:
|
| - while True:
|
| - e = g.next()
|
| -- # e might be builder.BuildStepStatus,
|
| -+ # e might be buildstep.BuildStepStatus,
|
| - # builder.BuildStatus, builder.Event,
|
| - # waterfall.Spacer(builder.Event), or changes.Change .
|
| - # The showEvents=False flag means we should hide
|
| - # builder.Event .
|
| - if not showEvents and isinstance(e, builder.Event):
|
| - continue
|
| -+
|
| -+ if isinstance(e, buildstep.BuildStepStatus):
|
| -+ # unfinished steps are always shown
|
| -+ if e.isFinished() and e.isHidden():
|
| -+ continue
|
| -+
|
| - break
|
| - event = interfaces.IStatusEvent(e)
|
| - if debug:
|
| ---
|
| -
|
| -
|
| -
|
| -
|
| -Applied f591d3369270b7897989c242fa7b827ca58b07b7 from buildbot upstream.
|
| -
|
| -
|
| -Add extra parameters to HttpStatusPush as a very basic authentication mechanism.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -index b7b3b0a..ca83fdb 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -@@ -328,7 +328,7 @@ class HttpStatusPush(StatusPush):
|
| -
|
| - def __init__(self, serverUrl, debug=None, maxMemoryItems=None,
|
| - maxDiskItems=None, chunkSize=200, maxHttpRequestSize=2**20,
|
| -- **kwargs):
|
| -+ extra_post_params=None, **kwargs):
|
| - """
|
| - @serverUrl: Base URL to be used to push events notifications.
|
| - @maxMemoryItems: Maximum number of items to keep queued in memory.
|
| -@@ -341,6 +341,7 @@ class HttpStatusPush(StatusPush):
|
| - """
|
| - # Parameters.
|
| - self.serverUrl = serverUrl
|
| -+ self.extra_post_params = extra_post_params or {}
|
| - self.debug = debug
|
| - self.chunkSize = chunkSize
|
| - self.lastPushWasSuccessful = True
|
| -@@ -378,7 +379,9 @@ class HttpStatusPush(StatusPush):
|
| - packets = json.dumps(items, indent=2, sort_keys=True)
|
| - else:
|
| - packets = json.dumps(items, separators=(',',':'))
|
| -- data = urllib.urlencode({'packets': packets})
|
| -+ params = {'packets': packets}
|
| -+ params.update(self.extra_post_params)
|
| -+ data = urllib.urlencode(params)
|
| - if (not self.maxHttpRequestSize or
|
| - len(data) < self.maxHttpRequestSize):
|
| - return (data, items)
|
| -@@ -395,6 +398,8 @@ class HttpStatusPush(StatusPush):
|
| -
|
| - def pushHttp(self):
|
| - """Do the HTTP POST to the server."""
|
| -+ if not self.serverUrl:
|
| -+ return
|
| - (encoded_packets, items) = self.popChunk()
|
| -
|
| - def Success(result):
|
| -
|
| -
|
| -
|
| -
|
| -Increase console customization build range.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -index b00b871..e513ec0 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -@@ -727,10 +727,10 @@ class ConsoleStatusResource(HtmlResource):
|
| - # Keep only the revisions we care about.
|
| - # By default we process the last 40 revisions.
|
| - # If a dev name is passed, we look for the changes by this person in the
|
| -- # last 80 revisions.
|
| -+ # last 160 revisions.
|
| - numRevs = int(request.args.get("revs", [40])[0])
|
| - if devName:
|
| -- numRevs *= 2
|
| -+ numRevs *= 4
|
| - numBuilds = numRevs
|
| -
|
| - # Get all changes we can find. This is a DB operation, so it must use
|
| -
|
| -
|
| -
|
| -Port console caching from 0.8.3p1 to 0.8.4p1.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -index 59cbc0e..c95ac7f 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -@@ -53,12 +53,74 @@ def getResultsClass(results, prevResults, inProgress):
|
| - else:
|
| - # The previous build also failed.
|
| - return "warnings"
|
| --
|
| -+
|
| - # Any other results? Like EXCEPTION?
|
| - return "exception"
|
| -
|
| - class ANYBRANCH: pass # a flag value, used below
|
| -
|
| -+class CachedStatusBox(object):
|
| -+ """Basic data class to remember the information for a box on the console."""
|
| -+ def __init__(self, color, pageTitle, details, url, tag):
|
| -+ self.color = color
|
| -+ self.pageTitle = pageTitle
|
| -+ self.details = details
|
| -+ self.url = url
|
| -+ self.tag = tag
|
| -+
|
| -+
|
| -+class CacheStatus(object):
|
| -+ """Basic cache of CachedStatusBox based on builder names and revisions.
|
| -+
|
| -+ Current limitation: If the revisions are not numerically increasing, the
|
| -+ "trim" feature will not work and the cache will grow
|
| -+ indefinitely.
|
| -+ """
|
| -+ def __init__(self):
|
| -+ self.allBoxes = dict()
|
| -+
|
| -+ def display(self):
|
| -+ """Display the available data in the cache. Used for debugging only."""
|
| -+ data = ""
|
| -+ for builder in self.allBoxes:
|
| -+ for revision in self.allBoxes[builder]:
|
| -+ data += "%s %s %s\n" % (builder, str(revision),
|
| -+ self.allBoxes[builder][revision].color)
|
| -+ return data
|
| -+
|
| -+ def insert(self, builderName, revision, color, pageTitle, details, url, tag):
|
| -+ """Insert a new build into the cache."""
|
| -+ box = CachedStatusBox(color, pageTitle, details, url, tag)
|
| -+ if not self.allBoxes.get(builderName):
|
| -+ self.allBoxes[builderName] = {}
|
| -+
|
| -+ self.allBoxes[builderName][revision] = box
|
| -+
|
| -+ def get(self, builderName, revision):
|
| -+ """Retrieve a build from the cache."""
|
| -+ if not self.allBoxes.get(builderName):
|
| -+ return None
|
| -+ if not self.allBoxes[builderName].get(revision):
|
| -+ return None
|
| -+ return self.allBoxes[builderName][revision]
|
| -+
|
| -+ def trim(self):
|
| -+ """Remove old revisions from the cache. (For integer revisions only)"""
|
| -+ try:
|
| -+ for builder in self.allBoxes:
|
| -+ allRevs = []
|
| -+ for revision in self.allBoxes[builder]:
|
| -+ allRevs.append(revision)
|
| -+
|
| -+ if len(allRevs) > 250:
|
| -+ allRevs.sort(cmp=lambda x,y: cmp(int(x), int(y)))
|
| -+ deleteCount = len(allRevs) - 250
|
| -+ for i in range(0, deleteCount):
|
| -+ del self.allBoxes[builder][allRevs[i]]
|
| -+ except:
|
| -+ pass
|
| -+
|
| -+
|
| - class DevRevision:
|
| - """Helper class that contains all the information we need for a revision."""
|
| -
|
| -@@ -97,6 +159,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - HtmlResource.__init__(self)
|
| -
|
| - self.status = None
|
| -+ self.cache = CacheStatus()
|
| -
|
| - if orderByTime:
|
| - self.comparator = TimeRevisionComparator()
|
| -@@ -133,22 +196,22 @@ class ConsoleStatusResource(HtmlResource):
|
| - def fetchChangesFromHistory(self, status, max_depth, max_builds, debugInfo):
|
| - """Look at the history of the builders and try to fetch as many changes
|
| - as possible. We need this when the main source does not contain enough
|
| -- sourcestamps.
|
| -+ sourcestamps.
|
| -
|
| - max_depth defines how many builds we will parse for a given builder.
|
| - max_builds defines how many builds total we want to parse. This is to
|
| - limit the amount of time we spend in this function.
|
| --
|
| -+
|
| - This function is sub-optimal, but the information returned by this
|
| - function is cached, so this function won't be called more than once.
|
| - """
|
| --
|
| -+
|
| - allChanges = list()
|
| - build_count = 0
|
| - for builderName in status.getBuilderNames()[:]:
|
| - if build_count > max_builds:
|
| - break
|
| --
|
| -+
|
| - builder = status.getBuilder(builderName)
|
| - build = self.getHeadBuild(builder)
|
| - depth = 0
|
| -@@ -160,7 +223,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - build = build.getPreviousBuild()
|
| -
|
| - debugInfo["source_fetch_len"] = len(allChanges)
|
| -- return allChanges
|
| -+ return allChanges
|
| -
|
| - @defer.deferredGenerator
|
| - def getAllChanges(self, request, status, debugInfo):
|
| -@@ -191,6 +254,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - prevChange = change
|
| - allChanges = newChanges
|
| -
|
| -+ debugInfo["source_len"] = len(allChanges)
|
| - yield allChanges
|
| -
|
| - def getBuildDetails(self, request, builderName, build):
|
| -@@ -232,7 +296,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - build, and we go down until we find a build that was built prior to the
|
| - last change we are interested in."""
|
| -
|
| -- revision = lastRevision
|
| -+ revision = lastRevision
|
| -
|
| - builds = []
|
| - build = self.getHeadBuild(builder)
|
| -@@ -299,7 +363,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - display the console page. The key is the builder name, and the value is
|
| - an array of build we care about. We also returns a dictionary of
|
| - builders we care about. The key is it's category.
|
| --
|
| -+
|
| - lastRevision is the last revision we want to display in the page.
|
| - categories is a list of categories to display. It is coming from the
|
| - HTTP GET parameters.
|
| -@@ -364,7 +428,7 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - cs = []
|
| -
|
| -- for category in categories:
|
| -+ for category in categories:
|
| - c = {}
|
| -
|
| - c["name"] = category
|
| -@@ -372,9 +436,9 @@ class ConsoleStatusResource(HtmlResource):
|
| - # To be able to align the table correctly, we need to know
|
| - # what percentage of space this category will be taking. This is
|
| - # (#Builders in Category) / (#Builders Total) * 100.
|
| -- c["size"] = (len(builderList[category]) * 100) / count
|
| -+ c["size"] = (len(builderList[category]) * 100) / count
|
| - cs.append(c)
|
| --
|
| -+
|
| - return cs
|
| -
|
| - def displaySlaveLine(self, status, builderList, debugInfo):
|
| -@@ -448,6 +512,23 @@ class ConsoleStatusResource(HtmlResource):
|
| - introducedIn = None
|
| - firstNotIn = None
|
| -
|
| -+ cached_value = self.cache.get(builder, revision.revision)
|
| -+ if cached_value:
|
| -+ debugInfo["from_cache"] += 1
|
| -+
|
| -+ b = {}
|
| -+ b["url"] = cached_value.url
|
| -+ b["pageTitle"] = cached_value.pageTitle
|
| -+ b["color"] = cached_value.color
|
| -+ b["tag"] = cached_value.tag
|
| -+
|
| -+ builds[category].append(b)
|
| -+
|
| -+ if cached_value.details and cached_value.color == "failure":
|
| -+ details.append(cached_value.details)
|
| -+
|
| -+ continue
|
| -+
|
| - # Find the first build that does not include the revision.
|
| - for build in allBuilds[builder]:
|
| - if self.comparator.isRevisionEarlier(build, revision):
|
| -@@ -504,6 +585,13 @@ class ConsoleStatusResource(HtmlResource):
|
| - if current_details and resultsClass == "failure":
|
| - details.append(current_details)
|
| -
|
| -+ # Add this box to the cache if it's completed so we don't have
|
| -+ # to compute it again.
|
| -+ if resultsClass not in ("running", "notstarted"):
|
| -+ debugInfo["added_blocks"] += 1
|
| -+ self.cache.insert(builder, revision.revision, resultsClass,
|
| -+ pageTitle, current_details, url, tag)
|
| -+
|
| - return (builds, details)
|
| -
|
| - def filterRevisions(self, revisions, filter=None, max_revs=None):
|
| -@@ -553,7 +641,8 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - if builderList:
|
| - subs["categories"] = self.displayCategories(builderList, debugInfo)
|
| -- subs['slaves'] = self.displaySlaveLine(status, builderList, debugInfo)
|
| -+ subs['slaves'] = self.displaySlaveLine(status, builderList,
|
| -+ debugInfo)
|
| - else:
|
| - subs["categories"] = []
|
| -
|
| -@@ -574,14 +663,14 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - # Display the status for all builders.
|
| - (builds, details) = self.displayStatusLine(builderList,
|
| -- allBuilds,
|
| -- revision,
|
| -- debugInfo)
|
| -+ allBuilds,
|
| -+ revision,
|
| -+ debugInfo)
|
| - r['builds'] = builds
|
| - r['details'] = details
|
| -
|
| - # Calculate the td span for the comment and the details.
|
| -- r["span"] = len(builderList) + 2
|
| -+ r["span"] = len(builderList) + 2
|
| -
|
| - subs['revisions'].append(r)
|
| -
|
| -@@ -678,6 +767,13 @@ class ConsoleStatusResource(HtmlResource):
|
| - debugInfo)
|
| -
|
| - debugInfo["added_blocks"] = 0
|
| -+ debugInfo["from_cache"] = 0
|
| -+
|
| -+ if request.args.get("display_cache", None):
|
| -+ data = ""
|
| -+ data += "\nGlobal Cache\n"
|
| -+ data += self.cache.display()
|
| -+ return data
|
| -
|
| - cxt.update(self.displayPage(request, status, builderList,
|
| - allBuilds, revisions, categories,
|
| -@@ -686,6 +782,11 @@ class ConsoleStatusResource(HtmlResource):
|
| - templates = request.site.buildbot_service.templates
|
| - template = templates.get_template("console.html")
|
| - data = template.render(cxt)
|
| -+
|
| -+ # Clean up the cache.
|
| -+ if debugInfo["added_blocks"]:
|
| -+ self.cache.trim()
|
| -+
|
| - return data
|
| - d.addCallback(got_changes)
|
| - return d
|
| -
|
| -
|
| -commit ca9f4ed52b4febcebd30ad77a8e40737f3a5ad1f
|
| -Author: Nicolas Sylvain <nsylvain@chromium.org>
|
| -Date: Fri Nov 23 12:24:04 2012 -0500
|
| -
|
| - Optionally add revision to the waterfall display of changes.
|
| -
|
| - Used in chromium's waterfalls extensively.
|
| - publicly reviewed here: https://codereview.chromium.org/7276032
|
| -
|
| - Modified to not change the look by default.
|
| -
|
| -diff --git a/master/buildbot/status/web/changes.py b/master/buildbot/status/web/changes.py
|
| -index 6be37b5..0971bea 100644
|
| ---- a/master/buildbot/status/web/changes.py
|
| -+++ b/master/buildbot/status/web/changes.py
|
| -@@ -63,7 +63,8 @@ class ChangeBox(components.Adapter):
|
| - template = req.site.buildbot_service.templates.get_template("change_macros.html")
|
| - text = template.module.box_contents(url=url,
|
| - who=self.original.getShortAuthor(),
|
| -- pageTitle=self.original.comments)
|
| -+ pageTitle=self.original.comments,
|
| -+ revision=self.original.revision)
|
| - return Box([text], class_="Change")
|
| - components.registerAdapter(ChangeBox, Change, IBox)
|
| -
|
| -diff --git a/master/buildbot/status/web/templates/change_macros.html b/master/buildbot/status/web/templates/change_macros.html
|
| -index 9b46191..dc6a9b2 100644
|
| ---- a/master/buildbot/status/web/templates/change_macros.html
|
| -+++ b/master/buildbot/status/web/templates/change_macros.html
|
| -@@ -71,6 +71,6 @@
|
| - {% endif %}
|
| - {%- endmacro %}
|
| -
|
| --{% macro box_contents(who, url, pageTitle) -%}
|
| -+{% macro box_contents(who, url, pageTitle, revision) -%}
|
| - <a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a>
|
| - {%- endmacro %}
|
| -
|
| -Add revision to the chromium waterfalls.
|
| -
|
| -Index: buildbot/status/web/templates/change_macros.html
|
| -===================================================================
|
| ---- buildbot/status/web/templates/change_macros.html (revision 167249)
|
| -+++ buildbot/status/web/templates/change_macros.html (working copy)
|
| -@@ -67,6 +67,6 @@
|
| - {% endif %}
|
| - {%- endmacro %}
|
| -
|
| - {% macro box_contents(who, url, pageTitle, revision) -%}
|
| --<a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a>
|
| -+<a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a><br>r{{ revision }}
|
| - {%- endmacro %}
|
| -
|
| -
|
| -
|
| -
|
| -commit e6b9fad4373d6e55f7957ee8312d58cf0461d98c
|
| -Author: Chase Phillips <cmp@google.com>
|
| -Date: Mon Jul 25 10:46:54 2011 -0700
|
| -
|
| - Import upstream fix: set journal mode.
|
| -
|
| - SQLite database fix from buildbot.net to increase
|
| - concurrency. This is 4050c5e7a:
|
| - https://github.com/buildbot/buildbot/commit/4050c5e7a2641df56f792b06fc1aea6c16221e8f#diff-0
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
|
| -index 7be9196..3d05cb6 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
|
| -@@ -25,6 +25,7 @@ special cases that Buildbot needs. Those include:
|
| -
|
| - import os
|
| - import sqlalchemy
|
| -+from twisted.python import log
|
| - from sqlalchemy.engine import strategies, url
|
| - from sqlalchemy.pool import NullPool
|
| -
|
| -@@ -83,6 +84,16 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStrategy):
|
| -
|
| - return u, kwargs, max_conns
|
| -
|
| -+ def set_up_sqlite_engine(self, u, engine):
|
| -+ """Special setup for sqlite engines"""
|
| -+ # try to enable WAL logging
|
| -+ if u.database:
|
| -+ log.msg("setting database journal mode to 'wal'")
|
| -+ try:
|
| -+ engine.execute("pragma journal_mode = wal")
|
| -+ except:
|
| -+ log.msg("failed to set journal mode - database may fail")
|
| -+
|
| - def special_case_mysql(self, u, kwargs):
|
| - """For mysql, take max_idle out of the query arguments, and
|
| - use its value for pool_recycle. Also, force use_unicode and
|
| -@@ -148,9 +159,12 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStrategy):
|
| - # by DBConnector to configure the surrounding thread pool
|
| - engine.optimal_thread_pool_size = max_conns
|
| -
|
| -- # and keep the basedir
|
| -+ # keep the basedir
|
| - engine.buildbot_basedir = basedir
|
| -
|
| -+ if u.drivername.startswith('sqlite'):
|
| -+ self.set_up_sqlite_engine(u, engine)
|
| -+
|
| - return engine
|
| -
|
| - BuildbotEngineStrategy()
|
| -
|
| -
|
| -
|
| -commit a2a0d76cbd2b016b628decf36ef8e298a9b1e4e8
|
| -Author: Chase Phillips <cmp@google.com>
|
| -Date: Thu Jul 28 16:24:09 2011 -0700
|
| -
|
| - Backport postgres fix from Buildbot trunk.
|
| -
|
| - This fixes http://trac.buildbot.net/ticket/2010.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py b/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py
|
| -index 650ac5d..b7d2b08 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/buildrequests.py
|
| -@@ -206,8 +206,6 @@ class BuildRequestsConnectorComponent(base.DBConnectorComponent):
|
| - master_incarnation = self.db.master.master_incarnation
|
| - tbl = self.db.model.buildrequests
|
| -
|
| -- transaction = conn.begin()
|
| --
|
| - # first, create a temporary table containing all of the ID's
|
| - # we want to claim
|
| - tmp_meta = sa.MetaData(bind=conn)
|
| -@@ -216,6 +214,8 @@ class BuildRequestsConnectorComponent(base.DBConnectorComponent):
|
| - prefixes=['TEMPORARY'])
|
| - tmp.create()
|
| -
|
| -+ transaction = conn.begin()
|
| -+
|
| - try:
|
| - q = tmp.insert()
|
| - conn.execute(q, [ dict(brid=id) for id in brids ])
|
| -@@ -268,8 +268,10 @@ class BuildRequestsConnectorComponent(base.DBConnectorComponent):
|
| - raise AlreadyClaimedError
|
| - res.close()
|
| - finally:
|
| -- # clean up after ourselves, even though it's a temporary table
|
| -- tmp.drop(checkfirst=True)
|
| -+ # clean up after ourselves, even though it's a temporary table;
|
| -+ # note that checkfirst=True does not work here for Postgres
|
| -+ # (#2010).
|
| -+ tmp.drop()
|
| -
|
| - return self.db.pool.do(thd)
|
| -
|
| -
|
| -Truncate commit comments to 1024 characters, which is the maximum size in the
|
| -db schema (see buildbot/db/model.py:181).
|
| -
|
| ---- buildbot/db/changes.py (revision 103214)
|
| -+++ buildbot/db/changes.py (working copy)
|
| -@@ -53,7 +53,7 @@
|
| - string)
|
| - """
|
| -
|
| -- def addChange(self, author=None, files=None, comments=None, is_dir=0,
|
| -+ def addChange(self, author=None, files=None, comments='', is_dir=0,
|
| - links=None, revision=None, when_timestamp=None, branch=None,
|
| - category=None, revlink='', properties={}, repository='',
|
| - project='', _reactor=reactor):
|
| -@@ -130,7 +130,7 @@
|
| - ins = self.db.model.changes.insert()
|
| - r = conn.execute(ins, dict(
|
| - author=author,
|
| -- comments=comments,
|
| -+ comments=comments[:1024],
|
| - is_dir=is_dir,
|
| - branch=branch,
|
| - revision=revision,
|
| -
|
| -
|
| -Fix inheritance bug.
|
| -
|
| -http://trac.buildbot.net/ticket/2120
|
| -
|
| -Index: buildbot/status/web/logs.py
|
| -===================================================================
|
| ---- buildbot/status/web/logs.py (revision 103214)
|
| -+++ buildbot/status/web/logs.py (working copy)
|
| -@@ -65,7 +65,7 @@
|
| - if path == "text":
|
| - self.asText = True
|
| - return self
|
| -- return HtmlResource.getChild(self, path, req)
|
| -+ return Resource.getChild(self, path, req)
|
| -
|
| - def content(self, entries):
|
| - html_entries = []
|
| -
|
| -
|
| -
|
| -**** Added by cmp on 10/4/2011
|
| -Fix updateSourceStamp=False to work as expected.
|
| -
|
| -Index: buildbot/steps/trigger.py
|
| -===================================================================
|
| ---- buildbot/steps/trigger.py
|
| -+++ buildbot/steps/trigger.py
|
| -@@ -77,15 +77,18 @@ class Trigger(LoggingBuildStep):
|
| -
|
| - """
|
| - assert schedulerNames, "You must specify a scheduler to trigger"
|
| -- if sourceStamp and updateSourceStamp:
|
| -+ if sourceStamp and (updateSourceStamp is not None):
|
| - raise ValueError("You can't specify both sourceStamp and updateSourceStamp")
|
| - if sourceStamp and alwaysUseLatest:
|
| - raise ValueError("You can't specify both sourceStamp and alwaysUseLatest")
|
| -- if alwaysUseLatest and updateSourceStamp:
|
| -+ if alwaysUseLatest and (updateSourceStamp is not None):
|
| - raise ValueError("You can't specify both alwaysUseLatest and updateSourceStamp")
|
| - self.schedulerNames = schedulerNames
|
| - self.sourceStamp = sourceStamp
|
| -- self.updateSourceStamp = updateSourceStamp or not (alwaysUseLatest or sourceStamp)
|
| -+ if updateSourceStamp is not None:
|
| -+ self.updateSourceStamp = updateSourceStamp
|
| -+ else:
|
| -+ self.updateSourceStamp = not (alwaysUseLatest or sourceStamp)
|
| - self.alwaysUseLatest = alwaysUseLatest
|
| - self.waitForFinish = waitForFinish
|
| - self.set_properties = set_properties
|
| -
|
| -
|
| -Merge from HEAD to pick up a bug fix.
|
| -
|
| -index 1c7b4ab..7474c0d 100644
|
| ---- a/master/buildbot/util/lru.py
|
| -+++ b/master/buildbot/util/lru.py
|
| -@@ -47,6 +47,7 @@ class AsyncLRUCache(object):
|
| - @ivar hits: cache hits so far
|
| - @ivar refhits: cache misses found in the weak ref dictionary, so far
|
| - @ivar misses: cache misses leading to re-fetches, so far
|
| -+ @ivar max_size: maximum allowed size of the cache
|
| - """
|
| -
|
| - __slots__ = ('max_size max_queue miss_fn '
|
| -@@ -72,7 +73,7 @@ class AsyncLRUCache(object):
|
| - self.weakrefs = WeakValueDictionary()
|
| - self.concurrent = {}
|
| - self.hits = self.misses = self.refhits = 0
|
| -- self.refcount = defaultdict(default_factory = lambda : 0)
|
| -+ self.refcount = defaultdict(lambda : 0)
|
| -
|
| - def get(self, key, **miss_fn_kwargs):
|
| - """
|
| -@@ -99,7 +100,7 @@ class AsyncLRUCache(object):
|
| - # utility function to record recent use of this key
|
| - def ref_key():
|
| - queue.append(key)
|
| -- refcount[key] = refcount.get(key, 0) + 1
|
| -+ refcount[key] = refcount[key] + 1
|
| -
|
| - # periodically compact the queue by eliminating duplicate keys
|
| - # while preserving order of most recent access. Note that this
|
| -@@ -151,11 +152,12 @@ class AsyncLRUCache(object):
|
| - cache[key] = result
|
| - weakrefs[key] = result
|
| -
|
| -- self._purge()
|
| -+ # reference the key once, possibly standing in for multiple
|
| -+ # concurrent accesses
|
| -+ ref_key()
|
| -
|
| -- # reference the key once, possibly standing in for multiple
|
| -- # concurrent accesses
|
| -- ref_key()
|
| -+ self.inv()
|
| -+ self._purge()
|
| -
|
| - # and fire all of the waiting Deferreds
|
| - dlist = concurrent.pop(key)
|
| -@@ -182,8 +184,8 @@ class AsyncLRUCache(object):
|
| - queue = self.queue
|
| - max_size = self.max_size
|
| -
|
| -- # purge least recently used entries, using refcount
|
| -- # to count repeatedly-used entries
|
| -+ # purge least recently used entries, using refcount to count entries
|
| -+ # that appear multiple times in the queue
|
| - while len(cache) > max_size:
|
| - refc = 1
|
| - while refc:
|
| -@@ -216,3 +218,31 @@ class AsyncLRUCache(object):
|
| - self.max_size = max_size
|
| - self.max_queue = max_size * self.QUEUE_SIZE_FACTOR
|
| - self._purge()
|
| -+
|
| -+ def inv(self):
|
| -+ """Check invariants and log if they are not met; used for debugging"""
|
| -+ global inv_failed
|
| -+
|
| -+ # the keys of the queue and cache should be identical
|
| -+ cache_keys = set(self.cache.keys())
|
| -+ queue_keys = set(self.queue)
|
| -+ if queue_keys - cache_keys:
|
| -+ log.msg("INV: uncached keys in queue:", queue_keys - cache_keys)
|
| -+ inv_failed = True
|
| -+ if cache_keys - queue_keys:
|
| -+ log.msg("INV: unqueued keys in cache:", cache_keys - queue_keys)
|
| -+ inv_failed = True
|
| -+
|
| -+ # refcount should always represent the number of times each key appears
|
| -+ # in the queue
|
| -+ exp_refcount = dict()
|
| -+ for k in self.queue:
|
| -+ exp_refcount[k] = exp_refcount.get(k, 0) + 1
|
| -+ if exp_refcount != self.refcount:
|
| -+ log.msg("INV: refcounts differ:")
|
| -+ log.msg(" expected:", sorted(exp_refcount.items()))
|
| -+ log.msg(" got:", sorted(self.refcount.items()))
|
| -+ inv_failed = True
|
| -+
|
| -+# for tests
|
| -+inv_failed = False
|
| -
|
| -
|
| -
|
| -Truncate comments to maximum length during migration.
|
| -
|
| -Index: db/migrate/versions/001_initial.py
|
| -===================================================================
|
| ---- db/migrate/versions/001_initial.py (revision 104114)
|
| -+++ db/migrate/versions/001_initial.py (working copy)
|
| -@@ -216,7 +216,7 @@
|
| - values = dict(
|
| - changeid=c.number,
|
| - author=c.who,
|
| -- comments=c.comments,
|
| -+ comments=c.comments[:1024],
|
| - is_dir=c.isdir,
|
| - branch=c.branch,
|
| - revision=c.revision,
|
| -
|
| -
|
| -Add the 'revlinktmpl' field to GitPoller, used the same way as in
|
| -SVNPoller.
|
| -
|
| -Index: buildbot/changes/gitpoller.py
|
| -===================================================================
|
| ---- buildbot/changes/gitpoller.py (revision 104853)
|
| -+++ buildbot/changes/gitpoller.py (working copy)
|
| -@@ -16,6 +16,7 @@
|
| - import time
|
| - import tempfile
|
| - import os
|
| -+import urllib
|
| - from twisted.python import log
|
| - from twisted.internet import defer, utils
|
| -
|
| -@@ -36,7 +37,7 @@
|
| - gitbin='git', usetimestamps=True,
|
| - category=None, project=None,
|
| - pollinterval=-2, fetch_refspec=None,
|
| -- encoding='utf-8'):
|
| -+ encoding='utf-8', revlinktmpl=''):
|
| - # for backward compatibility; the parameter used to be spelled with 'i'
|
| - if pollinterval != -2:
|
| - pollInterval = pollinterval
|
| -@@ -57,6 +58,7 @@
|
| - self.changeCount = 0
|
| - self.commitInfo = {}
|
| - self.initLock = defer.DeferredLock()
|
| -+ self.revlinktmpl = revlinktmpl
|
| -
|
| - if self.workdir == None:
|
| - self.workdir = tempfile.gettempdir() + '/gitpoller_work'
|
| -@@ -273,6 +275,10 @@
|
| - # just fail on the first error; they're probably all related!
|
| - raise failures[0]
|
| -
|
| -+ revlink = ''
|
| -+ if self.revlinktmpl and rev:
|
| -+ revlink = self.revlinktmpl % urllib.quote_plus(rev)
|
| -+
|
| - timestamp, name, files, comments = [ r[1] for r in results ]
|
| - d = self.master.addChange(
|
| - author=name,
|
| -@@ -283,7 +289,8 @@
|
| - branch=self.branch,
|
| - category=self.category,
|
| - project=self.project,
|
| -- repository=self.repourl)
|
| -+ repository=self.repourl,
|
| -+ revlink=revlink)
|
| - wfd = defer.waitForDeferred(d)
|
| - yield wfd
|
| - results = wfd.getResult()
|
| -
|
| -Add limit query argument to /console
|
| -
|
| -
|
| ---- buildbot/status/web/console.py
|
| -+++ buildbot/status/web/console.py
|
| -@@ -228,9 +228,9 @@ class ConsoleStatusResource(HtmlResource):
|
| - @defer.deferredGenerator
|
| - def getAllChanges(self, request, status, debugInfo):
|
| - master = request.site.buildbot_service.master
|
| --
|
| -+ limit = min(100, max(1, int(request.args.get('limit', [25])[0])))
|
| - wfd = defer.waitForDeferred(
|
| -- master.db.changes.getRecentChanges(25))
|
| -+ master.db.changes.getRecentChanges(limit))
|
| - yield wfd
|
| - chdicts = wfd.getResult()
|
| -
|
| -
|
| -
|
| -Also cache builderName used in console json-like data.
|
| -
|
| -Index: buildbot/status/web/console.py
|
| -===================================================================
|
| ---- buildbot/status/web/console.py
|
| -+++ buildbot/status/web/console.py
|
| -@@ -61,12 +61,13 @@ class ANYBRANCH: pass # a flag value, used below
|
| -
|
| - class CachedStatusBox(object):
|
| - """Basic data class to remember the information for a box on the console."""
|
| -- def __init__(self, color, pageTitle, details, url, tag):
|
| -+ def __init__(self, color, pageTitle, details, url, tag, builderName):
|
| - self.color = color
|
| - self.pageTitle = pageTitle
|
| - self.details = details
|
| - self.url = url
|
| - self.tag = tag
|
| -+ self.builderName = builderName
|
| -
|
| -
|
| - class CacheStatus(object):
|
| -@@ -90,7 +91,7 @@ class CacheStatus(object):
|
| -
|
| - def insert(self, builderName, revision, color, pageTitle, details, url, tag):
|
| - """Insert a new build into the cache."""
|
| -- box = CachedStatusBox(color, pageTitle, details, url, tag)
|
| -+ box = CachedStatusBox(color, pageTitle, details, url, tag, builderName)
|
| - if not self.allBoxes.get(builderName):
|
| - self.allBoxes[builderName] = {}
|
| -
|
| -@@ -467,6 +468,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - s["color"] = "notstarted"
|
| - s["pageTitle"] = builder
|
| - s["url"] = "./builders/%s" % urllib.quote(builder)
|
| -+ s["builderName"] = builder
|
| - state, builds = status.getBuilder(builder).getState()
|
| - # Check if it's offline, if so, the box is purple.
|
| - if state == "offline":
|
| -@@ -521,6 +523,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - b["pageTitle"] = cached_value.pageTitle
|
| - b["color"] = cached_value.color
|
| - b["tag"] = cached_value.tag
|
| -+ b["builderName"] = cached_value.builderName
|
| -
|
| - builds[category].append(b)
|
| -
|
| -@@ -577,6 +580,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - b["pageTitle"] = pageTitle
|
| - b["color"] = resultsClass
|
| - b["tag"] = tag
|
| -+ b["builderName"] = builder
|
| -
|
| - builds[category].append(b)
|
| -
|
| -
|
| -
|
| -
|
| -Re-add support for asHTML to Buildbot (used by ChromiumNotifier).
|
| -
|
| -Index: buildbot/changes/changes.py
|
| -===================================================================
|
| ---- buildbot/changes/changes.py
|
| -+++ buildbot/changes/changes.py
|
| -@@ -25,6 +25,25 @@ from buildbot.util import datetime2epoch
|
| - from buildbot import interfaces, util
|
| - from buildbot.process.properties import Properties
|
| -
|
| -+html_tmpl = """
|
| -+<p>Changed by: <b>%(who)s</b><br />
|
| -+Changed at: <b>%(at)s</b><br />
|
| -+%(repository)s
|
| -+%(branch)s
|
| -+%(revision)s
|
| -+<br />
|
| -+
|
| -+Changed files:
|
| -+%(files)s
|
| -+
|
| -+Comments:
|
| -+%(comments)s
|
| -+
|
| -+Properties:
|
| -+%(properties)s
|
| -+</p>
|
| -+"""
|
| -+
|
| - class Change:
|
| - """I represent a single change to the source tree. This may involve several
|
| - files, but they are all changed by the same person, and there is a change
|
| -@@ -181,6 +200,47 @@ class Change:
|
| - result['project'] = getattr(self, 'project', None)
|
| - return result
|
| -
|
| -+ def asHTML(self):
|
| -+ info = self.asDict()
|
| -+ links = []
|
| -+ for file in info['files']:
|
| -+ if file['url'] is not None:
|
| -+ # could get confused
|
| -+ links.append('<a href="%s"><b>%s</b></a>' % (file['url'], file['name']))
|
| -+ else:
|
| -+ links.append('<b>%s</b>' % file['name'])
|
| -+ if info['revision']:
|
| -+ if getattr(self, 'revlink', ""):
|
| -+ revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % (
|
| -+ info['revlink'], info['revision'])
|
| -+ else:
|
| -+ revision = "Revision: <b>%s</b><br />\n" % info['revision']
|
| -+ else:
|
| -+ revision = ''
|
| -+
|
| -+ if self.repository:
|
| -+ repository = "Repository: <b>%s</b><br />\n" % info['repository']
|
| -+ else:
|
| -+ repository = ''
|
| -+
|
| -+ branch = ""
|
| -+ if info['branch']:
|
| -+ branch = "Branch: <b>%s</b><br />\n" % info['branch']
|
| -+
|
| -+ properties = []
|
| -+ for prop in info['properties']:
|
| -+ properties.append("%s: %s<br />" % (prop[0], prop[1]))
|
| -+
|
| -+ kwargs = { 'who' : html.escape(info['who']),
|
| -+ 'at' : info['at'],
|
| -+ 'files' : html.UL(links) + '\n',
|
| -+ 'repository': repository,
|
| -+ 'revision' : revision,
|
| -+ 'branch' : branch,
|
| -+ 'comments' : html.PRE(info['comments']),
|
| -+ 'properties': html.UL(properties) + '\n' }
|
| -+ return html_tmpl % kwargs
|
| -+
|
| - def getShortAuthor(self):
|
| - return self.who
|
| -
|
| -
|
| -
|
| -
|
| -Add running_failure build status.
|
| -
|
| -Index: third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -===================================================================
|
| ---- third_party/buildbot_8_4p1/buildbot/status/web/console.py (revision 105249)
|
| -+++ third_party/buildbot_8_4p1/buildbot/status/web/console.py (working copy)
|
| -@@ -25,11 +25,27 @@
|
| -
|
| - class DoesNotPassFilter(Exception): pass # Used for filtering revs
|
| -
|
| --def getResultsClass(results, prevResults, inProgress):
|
| -+def isBuildGoingToFail(build):
|
| -+ """Returns True if one of the step in the running build has failed."""
|
| -+ for step in build.getSteps():
|
| -+ if step.getResults()[0] == builder.FAILURE:
|
| -+ return True
|
| -+ return False
|
| -+
|
| -+def getInProgressResults(build):
|
| -+ """Returns build status expectation for an incomplete build."""
|
| -+ if not build.isFinished() and isBuildGoingToFail(build):
|
| -+ return builder.FAILURE
|
| -+
|
| -+ return build.getResults()
|
| -+
|
| -+def getResultsClass(results, prevResults, inProgress, inProgressResults=None):
|
| - """Given the current and past results, return the class that will be used
|
| - by the css to display the right color for a box."""
|
| -
|
| - if inProgress:
|
| -+ if inProgressResults == builder.FAILURE:
|
| -+ return "running_failure"
|
| - return "running"
|
| -
|
| - if results is None:
|
| -@@ -139,7 +155,7 @@
|
| - class DevBuild:
|
| - """Helper class that contains all the information we need for a build."""
|
| -
|
| -- def __init__(self, revision, build, details):
|
| -+ def __init__(self, revision, build, details, inProgressResults=None):
|
| - self.revision = revision
|
| - self.results = build.getResults()
|
| - self.number = build.getNumber()
|
| -@@ -149,6 +165,7 @@
|
| - self.details = details
|
| - self.when = build.getTimes()[0]
|
| - self.source = build.getSourceStamp()
|
| -+ self.inProgressResults = inProgressResults
|
| -
|
| -
|
| - class ConsoleStatusResource(HtmlResource):
|
| -@@ -331,7 +348,8 @@
|
| - # user that his change might have broken the source update.
|
| - if got_rev and got_rev != -1:
|
| - details = self.getBuildDetails(request, builderName, build)
|
| -- devBuild = DevBuild(got_rev, build, details)
|
| -+ devBuild = DevBuild(got_rev, build, details,
|
| -+ getInProgressResults(build))
|
| - builds.append(devBuild)
|
| -
|
| - # Now break if we have enough builds.
|
| -@@ -543,9 +561,11 @@
|
| - # Get the results of the first build with the revision, and the
|
| - # first build that does not include the revision.
|
| - results = None
|
| -+ inProgressResults = None
|
| - previousResults = None
|
| - if introducedIn:
|
| - results = introducedIn.results
|
| -+ inProgressResults = introducedIn.inProgressResults
|
| - if firstNotIn:
|
| - previousResults = firstNotIn.results
|
| -
|
| -@@ -573,7 +593,8 @@
|
| - if isRunning:
|
| - pageTitle += ' ETA: %ds' % (introducedIn.eta or 0)
|
| -
|
| -- resultsClass = getResultsClass(results, previousResults, isRunning)
|
| -+ resultsClass = getResultsClass(results, previousResults, isRunning,
|
| -+ inProgressResults)
|
| -
|
| - b = {}
|
| - b["url"] = url
|
| -@@ -591,7 +612,8 @@
|
| -
|
| - # Add this box to the cache if it's completed so we don't have
|
| - # to compute it again.
|
| -- if resultsClass not in ("running", "notstarted"):
|
| -+ if resultsClass not in ("running", "running_failure",
|
| -+ "notstarted"):
|
| - debugInfo["added_blocks"] += 1
|
| - self.cache.insert(builder, revision.revision, resultsClass,
|
| - pageTitle, current_details, url, tag)
|
| -@@ -840,4 +862,3 @@
|
| -
|
| - def getSortingKey(self):
|
| - return operator.attrgetter('revision')
|
| --
|
| -
|
| -
|
| -Add diagnostic info for an error seen frequently in the wild.
|
| -
|
| -Index: buildbot/db/buildsets.py
|
| -===================================================================
|
| ---- buildbot/db/buildsets.py (revision 106949)
|
| -+++ buildbot/db/buildsets.py (working copy)
|
| -@@ -131,7 +131,9 @@
|
| - complete_at=_reactor.seconds())
|
| -
|
| - if res.rowcount != 1:
|
| -- raise KeyError
|
| -+ raise KeyError(('"SELECT * FROM buildsets WHERE id=%d AND '
|
| -+ 'complete != 1;" returned %d rows') % (
|
| -+ bsid, res.rowcount))
|
| - return self.db.pool.do(thd)
|
| -
|
| - def getBuildset(self, bsid):
|
| -@@ -291,4 +293,3 @@
|
| - complete=bool(row.complete),
|
| - complete_at=mkdt(row.complete_at), results=row.results,
|
| - bsid=row.id)
|
| --
|
| -
|
| -
|
| -Workaround incomplete Change object.
|
| -
|
| ---- buildbot/status/web/waterfall.py
|
| -+++ buildbot/status/web/waterfall.py
|
| -@@ -320,7 +320,7 @@ class ChangeEventSource(object):
|
| - continue
|
| - if categories and change.category not in categories:
|
| - continue
|
| -- if committers and change.author not in committers:
|
| -+ if committers and change.who not in committers:
|
| - continue
|
| - if minTime and change.when < minTime:
|
| - continue
|
| -
|
| -
|
| -Add back support for patch_subdir.
|
| -
|
| ---- buildbot/sourcestamp.py
|
| -+++ buildbot/sourcestamp.py
|
| -@@ -100,8 +100,8 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
|
| -
|
| - sourcestamp.patch = None
|
| - if ssdict['patch_body']:
|
| -- # note that this class does not store the patch_subdir
|
| -- sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body'])
|
| -+ sourcestamp.patch = (ssdict['patch_level'], ssdict['patch_body'],
|
| -+ ssdict.get('patch_subdir'))
|
| -
|
| - if ssdict['changeids']:
|
| - # sort the changeids in order, oldest to newest
|
| -@@ -129,7 +129,7 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
|
| - return
|
| -
|
| - if patch is not None:
|
| -- assert len(patch) == 2
|
| -+ assert 2 <= len(patch) <= 3
|
| - assert int(patch[0]) != -1
|
| - self.branch = branch
|
| - self.patch = patch
|
| -@@ -257,13 +257,17 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
|
| - # add it to the DB
|
| - patch_body = None
|
| - patch_level = None
|
| -+ patch_subdir = None
|
| - if self.patch:
|
| -- patch_level, patch_body = self.patch
|
| -+ patch_level = self.patch[0]
|
| -+ patch_body = self.patch[1]
|
| -+ if len(self.patch) > 2:
|
| -+ patch_subdir = self.patch[2]
|
| - d = master.db.sourcestamps.addSourceStamp(
|
| - branch=self.branch, revision=self.revision,
|
| - repository=self.repository, project=self.project,
|
| - patch_body=patch_body, patch_level=patch_level,
|
| -- patch_subdir=None, changeids=[c.number for c in self.changes])
|
| -+ patch_subdir=patch_subdir, changeids=[c.number for c in self.changes])
|
| - def set_ssid(ssid):
|
| - self.ssid = ssid
|
| - return ssid
|
| -
|
| -
|
| -Changes with a patch must never be merged.
|
| -
|
| ---- buildbot/sourcestamp.py
|
| -+++ buildbot/sourcestamp.py
|
| -@@ -162,6 +162,8 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
|
| - return False # the builds are completely unrelated
|
| - if other.project != self.project:
|
| - return False
|
| -+ if self.patch or other.patch:
|
| -+ return False # you can't merge patched builds with anything
|
| -
|
| - if self.changes and other.changes:
|
| - return True
|
| -@@ -170,8 +172,6 @@ class SourceStamp(util.ComparableMixin, styles.Versioned):
|
| - elif not self.changes and other.changes:
|
| - return False # they're using changes, we aren't
|
| -
|
| -- if self.patch or other.patch:
|
| -- return False # you can't merge patched builds with anything
|
| - if self.revision == other.revision:
|
| - # both builds are using the same specific revision, so they can
|
| - # be merged. It might be the case that revision==None, so they're
|
| -
|
| -
|
| -Add back 'reason' to json/builders/<builder>/pendingBuilds. It is necessary for
|
| -the commit queue.
|
| -
|
| ---- buildbot/status/buildrequest.py
|
| -+++ buildbot/status/buildrequest.py
|
| -@@ -143,10 +143,12 @@ class BuildRequestStatus:
|
| - result = {}
|
| -
|
| - wfd = defer.waitForDeferred(
|
| -- self.getSourceStamp())
|
| -+ self._getBuildRequest())
|
| - yield wfd
|
| -- ss = wfd.getResult()
|
| -+ br = wfd.getResult()
|
| -+ ss = br.source
|
| - result['source'] = ss.asDict()
|
| -+ result['reason'] = br.reason
|
| -
|
| - result['builderName'] = self.getBuilderName()
|
| -
|
| -
|
| -Fix exception on a race condition with slave disconnecting during a build trigger.
|
| -
|
| ---- third_party/buildbot_8_4p1/buildbot/process/slavebuilder.py
|
| -+++ third_party/buildbot_8_4p1/buildbot/process/slavebuilder.py
|
| -@@ -117,7 +117,7 @@ class AbstractSlaveBuilder(pb.Referenceable):
|
| - return d
|
| -
|
| - def prepare(self, builder_status, build):
|
| -- if not self.slave.acquireLocks():
|
| -+ if not self.slave or not self.slave.acquireLocks():
|
| - return defer.succeed(False)
|
| - return defer.succeed(True)
|
| -
|
| -
|
| -Fix exception on a race condition with slave disconnecting during a build trigger.
|
| -
|
| ---- third_party/buildbot_8_4p1/buildbot/process/builder.py
|
| -+++ third_party/buildbot_8_4p1/buildbot/process/builder.py
|
| -@@ -439,7 +439,8 @@ class Builder(pb.Referenceable, service.MultiService):
|
| - "request" % (build, slavebuilder))
|
| -
|
| - self.building.remove(build)
|
| -- slavebuilder.slave.releaseLocks()
|
| -+ if slavebuilder.slave:
|
| -+ slavebuilder.slave.releaseLocks()
|
| -
|
| - # release the buildrequest claims
|
| - wfd = defer.waitForDeferred(
|
| -
|
| -
|
| -Cherry-pick command list flattening functionality from upstream buildbot -
|
| -726e9f81c103939d22bd18d53ca65c66cfb8aed7.
|
| -
|
| ---- buildbot/steps/shell.py
|
| -+++ buildbot/steps/shell.py
|
| -@@ -222,6 +222,13 @@
|
| - # now prevent setupLogfiles() from adding them
|
| - self.logfiles = {}
|
| -
|
| -+ def _flattenList(self, mainlist, commands):
|
| -+ for x in commands:
|
| -+ if isinstance(x, (str, unicode)):
|
| -+ mainlist.append(x)
|
| -+ elif x != []:
|
| -+ self._flattenList(mainlist, x)
|
| -+
|
| - def start(self):
|
| - # this block is specific to ShellCommands. subclasses that don't need
|
| - # to set up an argv array, an environment, or extra logfiles= (like
|
| -@@ -231,8 +238,13 @@
|
| -
|
| - # create the actual RemoteShellCommand instance now
|
| - kwargs = self.remote_kwargs
|
| -- command = self.command
|
| -- kwargs['command'] = command
|
| -+ tmp = []
|
| -+ if isinstance(self.command, list):
|
| -+ self._flattenList(tmp, self.command)
|
| -+ else:
|
| -+ tmp = self.command
|
| -+
|
| -+ kwargs['command'] = tmp
|
| - kwargs['logfiles'] = self.logfiles
|
| -
|
| - # check for the usePTY flag
|
| -
|
| -
|
| -Replace the simple (and very slow) LRU cache implementation in BuilderStatus
|
| -with a better one. Added SyncLRUCache to avoid the expensive concurrency
|
| -protection in AsyncLRUCache.
|
| -
|
| -Index: buildbot/status/builder.py
|
| -===================================================================
|
| ---- buildbot/status/builder.py (revision 127129)
|
| -+++ buildbot/status/builder.py (working copy)
|
| -@@ -86,8 +86,8 @@
|
| - self.currentBuilds = []
|
| - self.nextBuild = None
|
| - self.watchers = []
|
| -- self.buildCache = weakref.WeakValueDictionary()
|
| -- self.buildCache_LRU = []
|
| -+ self.buildCache = util.lru.SyncLRUCache(self.cacheMiss,
|
| -+ self.buildCacheSize)
|
| - self.logCompressionLimit = False # default to no compression for tests
|
| - self.logCompressionMethod = "bz2"
|
| - self.logMaxSize = None # No default limit
|
| -@@ -103,7 +103,6 @@
|
| - d = styles.Versioned.__getstate__(self)
|
| - d['watchers'] = []
|
| - del d['buildCache']
|
| -- del d['buildCache_LRU']
|
| - for b in self.currentBuilds:
|
| - b.saveYourself()
|
| - # TODO: push a 'hey, build was interrupted' event
|
| -@@ -119,8 +118,8 @@
|
| - # when loading, re-initialize the transient stuff. Remember that
|
| - # upgradeToVersion1 and such will be called after this finishes.
|
| - styles.Versioned.__setstate__(self, d)
|
| -- self.buildCache = weakref.WeakValueDictionary()
|
| -- self.buildCache_LRU = []
|
| -+ self.buildCache = util.lru.SyncLRUCache(self.cacheMiss,
|
| -+ self.buildCacheSize)
|
| - self.currentBuilds = []
|
| - self.watchers = []
|
| - self.slavenames = []
|
| -@@ -132,6 +131,7 @@
|
| - # gets pickled and unpickled.
|
| - if buildmaster.buildCacheSize is not None:
|
| - self.buildCacheSize = buildmaster.buildCacheSize
|
| -+ self.buildCache.set_max_size(buildmaster.buildCacheSize)
|
| -
|
| - def upgradeToVersion1(self):
|
| - if hasattr(self, 'slavename'):
|
| -@@ -186,33 +186,17 @@
|
| - except:
|
| - log.msg("unable to save builder %s" % self.name)
|
| - log.err()
|
| --
|
| -
|
| -+
|
| - # build cache management
|
| -
|
| - def makeBuildFilename(self, number):
|
| - return os.path.join(self.basedir, "%d" % number)
|
| -
|
| -- def touchBuildCache(self, build):
|
| -- self.buildCache[build.number] = build
|
| -- if build in self.buildCache_LRU:
|
| -- self.buildCache_LRU.remove(build)
|
| -- self.buildCache_LRU = self.buildCache_LRU[-(self.buildCacheSize-1):] + [ build ]
|
| -- return build
|
| --
|
| - def getBuildByNumber(self, number):
|
| -- # first look in currentBuilds
|
| -- for b in self.currentBuilds:
|
| -- if b.number == number:
|
| -- return self.touchBuildCache(b)
|
| -+ return self.buildCache.get(number)
|
| -
|
| -- # then in the buildCache
|
| -- if number in self.buildCache:
|
| -- metrics.MetricCountEvent.log("buildCache.hits", 1)
|
| -- return self.touchBuildCache(self.buildCache[number])
|
| -- metrics.MetricCountEvent.log("buildCache.misses", 1)
|
| --
|
| -- # then fall back to loading it from disk
|
| -+ def loadBuildFromFile(self, number):
|
| - filename = self.makeBuildFilename(number)
|
| - try:
|
| - log.msg("Loading builder %s's build %d from on-disk pickle"
|
| -@@ -235,12 +219,20 @@
|
| - build.upgradeLogfiles()
|
| - # check that logfiles exist
|
| - build.checkLogfiles()
|
| -- return self.touchBuildCache(build)
|
| -+ return build
|
| - except IOError:
|
| - raise IndexError("no such build %d" % number)
|
| - except EOFError:
|
| - raise IndexError("corrupted build pickle %d" % number)
|
| -
|
| -+ def cacheMiss(self, number):
|
| -+ # first look in currentBuilds
|
| -+ for b in self.currentBuilds:
|
| -+ if b.number == number:
|
| -+ return b
|
| -+ # then fall back to loading it from disk
|
| -+ return self.loadBuildFromFile(number)
|
| -+
|
| - def prune(self, events_only=False):
|
| - # begin by pruning our own events
|
| - self.events = self.events[-self.eventHorizon:]
|
| -@@ -287,7 +279,7 @@
|
| - is_logfile = True
|
| -
|
| - if num is None: continue
|
| -- if num in self.buildCache: continue
|
| -+ if num in self.buildCache.cache: continue
|
| -
|
| - if (is_logfile and num < earliest_log) or num < earliest_build:
|
| - pathname = os.path.join(self.basedir, filename)
|
| -@@ -510,7 +502,7 @@
|
| - assert s.number == self.nextBuildNumber - 1
|
| - assert s not in self.currentBuilds
|
| - self.currentBuilds.append(s)
|
| -- self.touchBuildCache(s)
|
| -+ self.buildCache.put(s.number, s)
|
| -
|
| - # now that the BuildStatus is prepared to answer queries, we can
|
| - # announce the new build to all our watchers
|
| -@@ -620,7 +612,7 @@
|
| - # Collect build numbers.
|
| - # Important: Only grab the *cached* builds numbers to reduce I/O.
|
| - current_builds = [b.getNumber() for b in self.currentBuilds]
|
| -- cached_builds = list(set(self.buildCache.keys() + current_builds))
|
| -+ cached_builds = list(set(self.buildCache.cache.keys() + current_builds))
|
| - cached_builds.sort()
|
| - result['cachedBuilds'] = cached_builds
|
| - result['currentBuilds'] = current_builds
|
| -Index: buildbot/util/lru.py
|
| -===================================================================
|
| ---- buildbot/util/lru.py (revision 127129)
|
| -+++ buildbot/util/lru.py (working copy)
|
| -@@ -244,5 +244,82 @@
|
| - log.msg(" got:", sorted(self.refcount.items()))
|
| - inv_failed = True
|
| -
|
| -+
|
| -+class SyncLRUCache(AsyncLRUCache):
|
| -+ """
|
| -+
|
| -+ A least-recently-used cache using the same strategy as AsyncLRUCache,
|
| -+ minus the protections for concurrent access. The motivation for this
|
| -+ class is to provide a speedier implementation for heavily-used caches
|
| -+ that don't need the concurrency protections.
|
| -+
|
| -+ The constructor takes the same arguments as the AsyncLRUCache
|
| -+ constructor, except C{miss_fn} must return the missing value, I{not} a
|
| -+ deferred.
|
| -+ """
|
| -+
|
| -+ # utility function to record recent use of this key
|
| -+ def _ref_key(key):
|
| -+ refcount = self.refcount
|
| -+ queue = self.queue
|
| -+
|
| -+ queue.append(key)
|
| -+ refcount[key] = refcount[key] + 1
|
| -+
|
| -+ # periodically compact the queue by eliminating duplicate keys
|
| -+ # while preserving order of most recent access. Note that this
|
| -+ # is only required when the cache does not exceed its maximum
|
| -+ # size
|
| -+ if len(queue) > self.max_queue:
|
| -+ refcount.clear()
|
| -+ queue_appendleft = queue.appendleft
|
| -+ queue_appendleft(self.sentinel)
|
| -+ for k in ifilterfalse(refcount.__contains__,
|
| -+ iter(queue.pop, self.sentinel)):
|
| -+ queue_appendleft(k)
|
| -+ refcount[k] = 1
|
| -+
|
| -+ def get(self, key, **miss_fn_kwargs):
|
| -+ """
|
| -+ Fetch a value from the cache by key, invoking C{self.miss_fn(key)} if
|
| -+ the key is not in the cache.
|
| -+
|
| -+ No protection is provided against concurrent access.
|
| -+
|
| -+ @param key: cache key
|
| -+ @param **miss_fn_kwargs: keyword arguments to the miss_fn
|
| -+ @returns: cache value
|
| -+ """
|
| -+ cache = self.cache
|
| -+ weakrefs = self.weakrefs
|
| -+
|
| -+ try:
|
| -+ result = cache[key]
|
| -+ self.hits += 1
|
| -+ self._ref_key(key)
|
| -+ return result
|
| -+ except KeyError:
|
| -+ try:
|
| -+ result = weakrefs[key]
|
| -+ self.refhits += 1
|
| -+ cache[key] = result
|
| -+ self._ref_key(key)
|
| -+ return result
|
| -+ except KeyError:
|
| -+ pass
|
| -+
|
| -+ # if we're here, we've missed and need to fetch
|
| -+ self.misses += 1
|
| -+
|
| -+ result = self.miss_fn(key, **miss_fn_kwargs)
|
| -+ if result is not None:
|
| -+ cache[key] = result
|
| -+ weakrefs[key] = result
|
| -+ self._ref_key(key)
|
| -+ self._purge()
|
| -+
|
| -+ return result
|
| -+
|
| -+
|
| - # for tests
|
| - inv_failed = False
|
| -Index: buildbot/test/unit/test_status_builder_cache.py
|
| -===================================================================
|
| ---- buildbot/test/unit/test_status_builder_cache.py (revision 0)
|
| -+++ buildbot/test/unit/test_status_builder_cache.py (revision 0)
|
| -@@ -0,0 +1,60 @@
|
| -+# This file is part of Buildbot. Buildbot is free software: you can
|
| -+# redistribute it and/or modify it under the terms of the GNU General Public
|
| -+# License as published by the Free Software Foundation, version 2.
|
| -+#
|
| -+# This program is distributed in the hope that it will be useful, but WITHOUT
|
| -+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| -+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
| -+# details.
|
| -+#
|
| -+# You should have received a copy of the GNU General Public License along with
|
| -+# this program; if not, write to the Free Software Foundation, Inc., 51
|
| -+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| -+#
|
| -+# Copyright Buildbot Team Members
|
| -+
|
| -+import os
|
| -+from mock import Mock
|
| -+from twisted.trial import unittest
|
| -+from buildbot.status import builder, master
|
| -+
|
| -+class TestBuildStatus(unittest.TestCase):
|
| -+
|
| -+ # that buildstep.BuildStepStatus is never instantiated here should tell you
|
| -+ # that these classes are not well isolated!
|
| -+
|
| -+ def setupBuilder(self, buildername, category=None):
|
| -+ b = builder.BuilderStatus(buildername=buildername, category=category)
|
| -+ # Ackwardly, Status sets this member variable.
|
| -+ b.basedir = os.path.abspath(self.mktemp())
|
| -+ os.mkdir(b.basedir)
|
| -+ # Otherwise, builder.nextBuildNumber is not defined.
|
| -+ b.determineNextBuildNumber()
|
| -+ # Must initialize these fields before pickling.
|
| -+ b.currentBigState = 'idle'
|
| -+ b.status = 'idle'
|
| -+ return b
|
| -+
|
| -+ def setupStatus(self, b):
|
| -+ m = Mock()
|
| -+ m.buildbotURL = 'http://buildbot:8010/'
|
| -+ m.basedir = '/basedir'
|
| -+ s = master.Status(m)
|
| -+ b.status = s
|
| -+ return s
|
| -+
|
| -+ def testBuildCache(self):
|
| -+ b = self.setupBuilder('builder_1')
|
| -+ builds = []
|
| -+ for i in xrange(5):
|
| -+ build = b.newBuild()
|
| -+ build.setProperty('propkey', 'propval%d' % i, 'test')
|
| -+ builds.append(build)
|
| -+ build.buildStarted(build)
|
| -+ build.buildFinished()
|
| -+ for build in builds:
|
| -+ build2 = b.getBuild(build.number)
|
| -+ self.assertTrue(build2)
|
| -+ self.assertEqual(build2.number, build.number)
|
| -+ self.assertEqual(build2.getProperty('propkey'),
|
| -+ 'propval%d' % build.number)
|
| -+ # Do another round, to make sure we're hitting the cache
|
| -+ hits = b.buildCache.hits
|
| -+ for build in builds:
|
| -+ build2 = b.getBuild(build.number)
|
| -+ self.assertTrue(build2)
|
| -+ self.assertEqual(build2.number, build.number)
|
| -+ self.assertEqual(build2.getProperty('propkey'),
|
| -+ 'propval%d' % build.number)
|
| -+ self.assertEqual(b.buildCache.hits, hits+1)
|
| -+ hits = hits + 1
|
| -
|
| -Import patch from buildbot trunk to remove unnecessary calls to
|
| -gc.collect, which cause synchronous hangs in the master.
|
| -
|
| -Index: buildbot/status/builder.py
|
| -===================================================================
|
| ---- buildbot/status/builder.py (revision 128258)
|
| -+++ buildbot/status/builder.py (working copy)
|
| -@@ -239,8 +239,6 @@
|
| - if events_only:
|
| - return
|
| -
|
| -- gc.collect()
|
| --
|
| - # get the horizons straight
|
| - if self.buildHorizon is not None:
|
| - earliest_build = self.nextBuildNumber - self.buildHorizon
|
| -
|
| -
|
| -
|
| -Add a handler for the signal USR1 that cancels all new builds.
|
| -
|
| -Index: buildbot/master.py
|
| ---- buildbot/master.py
|
| -+++ buildbot/master.py
|
| -@@ -161,6 +161,8 @@ class BuildMaster(service.MultiService):
|
| - self.loadTheConfigFile()
|
| - if hasattr(signal, "SIGHUP"):
|
| - signal.signal(signal.SIGHUP, self._handleSIGHUP)
|
| -+ if hasattr(signal, "SIGUSR1"):
|
| -+ signal.signal(signal.SIGUSR1, self._handleSIGUSR1)
|
| - for b in self.botmaster.builders.values():
|
| - b.builder_status.addPointEvent(["master", "started"])
|
| - b.builder_status.saveYourself()
|
| -@@ -168,12 +170,51 @@ class BuildMaster(service.MultiService):
|
| - def _handleSIGHUP(self, *args):
|
| - reactor.callLater(0, self.loadTheConfigFile)
|
| -
|
| -+ def _handleSIGUSR1(self, *args):
|
| -+ reactor.callLater(0, self.noNewBuilds)
|
| -+
|
| - def getStatus(self):
|
| - """
|
| - @rtype: L{buildbot.status.builder.Status}
|
| - """
|
| - return self.status
|
| -
|
| -+ @defer.deferredGenerator
|
| -+ def cancelAllPendingBuilds(self):
|
| -+ log.msg("canceling pending builds")
|
| -+ c = interfaces.IControl(self)
|
| -+ for bname in self.botmaster.builders:
|
| -+ builder_control = c.getBuilder(bname)
|
| -+ wfd = defer.waitForDeferred(
|
| -+ builder_control.getPendingBuildRequestControls())
|
| -+ yield wfd
|
| -+ brcontrols = wfd.getResult()
|
| -+ build_controls = dict((x.brid, x) for x in brcontrols)
|
| -+ builder_status = self.status.getBuilder(bname)
|
| -+ wfd = defer.waitForDeferred(
|
| -+ builder_status.getPendingBuildRequestStatuses())
|
| -+ yield wfd
|
| -+ build_req_statuses = wfd.getResult()
|
| -+ number_cancelled_builds = 0
|
| -+ for build_req in build_req_statuses:
|
| -+ control = build_controls[build_req.brid]
|
| -+ control.cancel()
|
| -+ number_cancelled_builds += 1
|
| -+ log.msg("builder '%s' cancelled %d pending builds" % (
|
| -+ bname, number_cancelled_builds))
|
| -+
|
| -+ def noNewBuilds(self):
|
| -+ log.msg("stopping schedulers")
|
| -+ self.loadConfig_Schedulers([])
|
| -+ log.msg("stopping sources")
|
| -+ self.loadConfig_Sources([])
|
| -+ d = self.cancelAllPendingBuilds()
|
| -+ def doneStopping(res):
|
| -+ log.msg("new builds stopped")
|
| -+ return res
|
| -+ d.addCallback(doneStopping)
|
| -+ return d
|
| -+
|
| - def loadTheConfigFile(self, configFile=None):
|
| - if not configFile:
|
| - configFile = os.path.join(self.basedir, self.configFileName)
|
| -
|
| -
|
| -Use weakrefs to avoid circular references between status objects.
|
| -
|
| -http://codereview.chromium.org/9991001/
|
| -
|
| -Index: buildbot/status/master.py
|
| -===================================================================
|
| ---- buildbot/status/master.py (revision 132430)
|
| -+++ buildbot/status/master.py (working copy)
|
| -@@ -124,7 +124,7 @@
|
| -
|
| - logs = step.getLogs()
|
| - for i in range(len(logs)):
|
| -- if loog is logs[i]:
|
| -+ if loog.getName() == logs[i].getName():
|
| - break
|
| - else:
|
| - return None
|
| -Index: buildbot/status/buildstep.py
|
| -===================================================================
|
| ---- buildbot/status/buildstep.py (revision 132430)
|
| -+++ buildbot/status/buildstep.py (working copy)
|
| -@@ -13,7 +13,7 @@
|
| - #
|
| - # Copyright Buildbot Team Members
|
| -
|
| --import os
|
| -+import os, weakref
|
| - from zope.interface import implements
|
| - from twisted.persisted import styles
|
| - from twisted.python import log
|
| -@@ -63,7 +63,9 @@
|
| -
|
| - def __init__(self, parent, step_number):
|
| - assert interfaces.IBuildStatus(parent)
|
| -- self.build = parent
|
| -+ self.build = weakref.ref(parent)
|
| -+ self.build_number = parent.getNumber()
|
| -+ self.builder = parent.getBuilder()
|
| - self.step_number = step_number
|
| - self.logs = []
|
| - self.urls = {}
|
| -@@ -81,7 +83,12 @@
|
| - return self.name
|
| -
|
| - def getBuild(self):
|
| -- return self.build
|
| -+ result = self.build()
|
| -+ if result is not None:
|
| -+ return result
|
| -+ result = self.builder.getBuildByNumber(self.build_number)
|
| -+ self.build = weakref.ref(result)
|
| -+ return result
|
| -
|
| - def getTimes(self):
|
| - return (self.started, self.finished)
|
| -@@ -179,7 +186,7 @@
|
| - def sendETAUpdate(self, receiver, updateInterval):
|
| - self.updates[receiver] = None
|
| - # they might unsubscribe during stepETAUpdate
|
| -- receiver.stepETAUpdate(self.build, self,
|
| -+ receiver.stepETAUpdate(self.getBuild(), self,
|
| - self.getETA(), self.getExpectations())
|
| - if receiver in self.watchers:
|
| - self.updates[receiver] = reactor.callLater(updateInterval,
|
| -@@ -209,19 +216,21 @@
|
| -
|
| - def stepStarted(self):
|
| - self.started = util.now()
|
| -- if self.build:
|
| -- self.build.stepStarted(self)
|
| -+ build = self.getBuild()
|
| -+ if build:
|
| -+ build.stepStarted(self)
|
| -
|
| - def addLog(self, name):
|
| - assert self.started # addLog before stepStarted won't notify watchers
|
| -- logfilename = self.build.generateLogfileName(self.name, name)
|
| -+ build = self.getBuild()
|
| -+ logfilename = build.generateLogfileName(self.name, name)
|
| - log = LogFile(self, name, logfilename)
|
| -- log.logMaxSize = self.build.builder.logMaxSize
|
| -- log.logMaxTailSize = self.build.builder.logMaxTailSize
|
| -- log.compressMethod = self.build.builder.logCompressionMethod
|
| -+ log.logMaxSize = self.builder.logMaxSize
|
| -+ log.logMaxTailSize = self.builder.logMaxTailSize
|
| -+ log.compressMethod = self.builder.logCompressionMethod
|
| - self.logs.append(log)
|
| - for w in self.watchers:
|
| -- receiver = w.logStarted(self.build, self, log)
|
| -+ receiver = w.logStarted(build, self, log)
|
| - if receiver:
|
| - log.subscribe(receiver, True)
|
| - d = log.waitUntilFinished()
|
| -@@ -232,28 +241,32 @@
|
| -
|
| - def addHTMLLog(self, name, html):
|
| - assert self.started # addLog before stepStarted won't notify watchers
|
| -- logfilename = self.build.generateLogfileName(self.name, name)
|
| -+ build = self.getBuild()
|
| -+ logfilename = build.generateLogfileName(self.name, name)
|
| - log = HTMLLogFile(self, name, logfilename, html)
|
| - self.logs.append(log)
|
| - for w in self.watchers:
|
| -- w.logStarted(self.build, self, log)
|
| -- w.logFinished(self.build, self, log)
|
| -+ w.logStarted(build, self, log)
|
| -+ w.logFinished(build, self, log)
|
| -
|
| - def logFinished(self, log):
|
| -+ build = self.getBuild()
|
| - for w in self.watchers:
|
| -- w.logFinished(self.build, self, log)
|
| -+ w.logFinished(build, self, log)
|
| -
|
| - def addURL(self, name, url):
|
| - self.urls[name] = url
|
| -
|
| - def setText(self, text):
|
| - self.text = text
|
| -+ build = self.getBuild()
|
| - for w in self.watchers:
|
| -- w.stepTextChanged(self.build, self, text)
|
| -+ w.stepTextChanged(build, self, text)
|
| - def setText2(self, text):
|
| - self.text2 = text
|
| -+ build = self.getBuild()
|
| - for w in self.watchers:
|
| -- w.stepText2Changed(self.build, self, text)
|
| -+ w.stepText2Changed(build, self, text)
|
| -
|
| - def setStatistic(self, name, value):
|
| - """Set the given statistic. Usually called by subclasses.
|
| -@@ -267,7 +280,7 @@
|
| - self.finished = util.now()
|
| - self.results = results
|
| - cld = [] # deferreds for log compression
|
| -- logCompressionLimit = self.build.builder.logCompressionLimit
|
| -+ logCompressionLimit = self.builder.logCompressionLimit
|
| - for loog in self.logs:
|
| - if not loog.isFinished():
|
| - loog.finish()
|
| -@@ -307,6 +320,7 @@
|
| - def __getstate__(self):
|
| - d = styles.Versioned.__getstate__(self)
|
| - del d['build'] # filled in when loading
|
| -+ del d['builder'] # filled in when loading
|
| - if d.has_key('progress'):
|
| - del d['progress']
|
| - del d['watchers']
|
| -@@ -316,11 +330,11 @@
|
| -
|
| - def __setstate__(self, d):
|
| - styles.Versioned.__setstate__(self, d)
|
| -- # self.build must be filled in by our parent
|
| -+ # self.build and self.builder must be filled in by our parent
|
| -
|
| - # point the logs to this object
|
| - for loog in self.logs:
|
| -- loog.step = self
|
| -+ loog.step = weakref.ref(self)
|
| - self.watchers = []
|
| - self.finishedWatchers = []
|
| - self.updates = {}
|
| -@@ -357,8 +371,6 @@
|
| - result['urls'] = self.getURLs()
|
| - result['step_number'] = self.step_number
|
| - result['logs'] = [[l.getName(),
|
| -- self.build.builder.status.getURLForThing(l)]
|
| -+ self.builder.status.getURLForThing(l)]
|
| - for l in self.getLogs()]
|
| - return result
|
| --
|
| --
|
| -Index: buildbot/status/build.py
|
| -===================================================================
|
| ---- buildbot/status/build.py (revision 132430)
|
| -+++ buildbot/status/build.py (working copy)
|
| -@@ -13,7 +13,7 @@
|
| - #
|
| - # Copyright Buildbot Team Members
|
| -
|
| --import os, shutil, re
|
| -+import os, shutil, re, weakref
|
| - from cPickle import dump
|
| - from zope.interface import implements
|
| - from twisted.python import log, runtime
|
| -@@ -361,7 +361,7 @@
|
| - styles.Versioned.__setstate__(self, d)
|
| - # self.builder must be filled in by our parent when loading
|
| - for step in self.steps:
|
| -- step.build = self
|
| -+ step.build = weakref.ref(self)
|
| - self.watchers = []
|
| - self.updates = {}
|
| - self.finishedWatchers = []
|
| -@@ -459,6 +459,3 @@
|
| - else:
|
| - result['currentStep'] = None
|
| - return result
|
| --
|
| --
|
| --
|
| -Index: buildbot/status/buildrequest.py
|
| -===================================================================
|
| ---- buildbot/status/buildrequest.py (revision 132430)
|
| -+++ buildbot/status/buildrequest.py (working copy)
|
| -@@ -70,10 +70,6 @@
|
| -
|
| - yield self._buildrequest
|
| -
|
| -- def buildStarted(self, build):
|
| -- self.status._buildrequest_buildStarted(build.status)
|
| -- self.builds.append(build.status)
|
| --
|
| - # methods called by our clients
|
| - @defer.deferredGenerator
|
| - def getSourceStamp(self):
|
| -Index: buildbot/status/logfile.py
|
| -===================================================================
|
| ---- buildbot/status/logfile.py (revision 132430)
|
| -+++ buildbot/status/logfile.py (working copy)
|
| -@@ -13,7 +13,7 @@
|
| - #
|
| - # Copyright Buildbot Team Members
|
| -
|
| --import os
|
| -+import os, weakref
|
| - from cStringIO import StringIO
|
| - from bz2 import BZ2File
|
| - from gzip import GzipFile
|
| -@@ -216,7 +216,10 @@
|
| - @type logfilename: string
|
| - @param logfilename: the Builder-relative pathname for the saved entries
|
| - """
|
| -- self.step = parent
|
| -+ self.step = weakref.ref(parent)
|
| -+ self.step_number = parent.step_number
|
| -+ self.build_number = parent.getBuild().getNumber()
|
| -+ self.builder = parent.builder
|
| - self.name = name
|
| - self.filename = logfilename
|
| - fn = self.getFilename()
|
| -@@ -236,7 +239,7 @@
|
| - self.tailBuffer = []
|
| -
|
| - def getFilename(self):
|
| -- return os.path.join(self.step.build.builder.basedir, self.filename)
|
| -+ return os.path.join(self.builder.basedir, self.filename)
|
| -
|
| - def hasContents(self):
|
| - return os.path.exists(self.getFilename() + '.bz2') or \
|
| -@@ -247,7 +250,13 @@
|
| - return self.name
|
| -
|
| - def getStep(self):
|
| -- return self.step
|
| -+ result = self.step()
|
| -+ if result is not None:
|
| -+ return result
|
| -+ build = self.builder.getBuildByNumber(self.build_number)
|
| -+ result = build.getSteps()[self.step_number]
|
| -+ self.step = weakref.ref(result)
|
| -+ return result
|
| -
|
| - def isFinished(self):
|
| - return self.finished
|
| -@@ -368,7 +377,7 @@
|
| - if catchup:
|
| - for channel, text in self.getChunks():
|
| - # TODO: add logChunks(), to send over everything at once?
|
| -- receiver.logChunk(self.step.build, self.step, self,
|
| -+ receiver.logChunk(self.getStep().getBuild(), self.getStep(), self,
|
| - channel, text)
|
| -
|
| - def unsubscribe(self, receiver):
|
| -@@ -439,8 +448,10 @@
|
| - if self.runLength >= self.chunkSize:
|
| - self.merge()
|
| -
|
| -+ step = self.getStep()
|
| -+ build = step.getBuild()
|
| - for w in self.watchers:
|
| -- w.logChunk(self.step.build, self.step, self, channel, text)
|
| -+ w.logChunk(build, step, self, channel, text)
|
| - self.length += len(text)
|
| -
|
| - def addStdout(self, text):
|
| -@@ -526,6 +537,7 @@
|
| - def __getstate__(self):
|
| - d = self.__dict__.copy()
|
| - del d['step'] # filled in upon unpickling
|
| -+ del d['builder'] # filled in upon unpickling
|
| - del d['watchers']
|
| - del d['finishedWatchers']
|
| - d['entries'] = [] # let 0.6.4 tolerate the saved log. TODO: really?
|
| -@@ -539,7 +551,7 @@
|
| - self.__dict__ = d
|
| - self.watchers = [] # probably not necessary
|
| - self.finishedWatchers = [] # same
|
| -- # self.step must be filled in by our parent
|
| -+ # self.step and self.builder must be filled in by our parent
|
| - self.finished = True
|
| -
|
| - def upgrade(self, logfilename):
|
| -@@ -561,7 +573,10 @@
|
| - filename = None
|
| -
|
| - def __init__(self, parent, name, logfilename, html):
|
| -- self.step = parent
|
| -+ self.step = weakref.ref(parent)
|
| -+ self.step_number = parent.step_number
|
| -+ self.build_number = parent.getBuild().getNumber()
|
| -+ self.builder = parent.builder
|
| - self.name = name
|
| - self.filename = logfilename
|
| - self.html = html
|
| -@@ -569,7 +584,13 @@
|
| - def getName(self):
|
| - return self.name # set in BuildStepStatus.addLog
|
| - def getStep(self):
|
| -- return self.step
|
| -+ result = self.step()
|
| -+ if result is not None:
|
| -+ return result
|
| -+ build = self.builder.getBuildByNumber(self.build_number)
|
| -+ result = build.getSteps()[self.step_number]
|
| -+ self.step = weakref.ref(result)
|
| -+ return result
|
| -
|
| - def isFinished(self):
|
| - return True
|
| -@@ -596,6 +617,7 @@
|
| - def __getstate__(self):
|
| - d = self.__dict__.copy()
|
| - del d['step']
|
| -+ del d['builder']
|
| - return d
|
| -
|
| - def upgrade(self, logfilename):
|
| -@@ -617,4 +639,3 @@
|
| - else:
|
| - log.msg("giving up on removing %s after over %d seconds" %
|
| - (filename, timeout))
|
| --
|
| -Index: buildbot/status/builder.py
|
| -===================================================================
|
| ---- buildbot/status/builder.py (revision 132430)
|
| -+++ buildbot/status/builder.py (working copy)
|
| -@@ -202,6 +202,10 @@
|
| - % (self.name, number))
|
| - build = load(open(filename, "rb"))
|
| - build.builder = self
|
| -+ for step in build.getSteps():
|
| -+ step.builder = self
|
| -+ for loog in step.getLogs():
|
| -+ loog.builder = self
|
| -
|
| - # (bug #1068) if we need to upgrade, we probably need to rewrite
|
| - # this pickle, too. We determine this by looking at the list of
|
| -
|
| -
|
| -Add nextSlaveAndBuilder to builder config
|
| -
|
| -https://chromiumcodereview.appspot.com/10032026/
|
| -
|
| -Index: buildbot/config.py
|
| -===================================================================
|
| ---- buildbot/config.py (revision 132861)
|
| -+++ buildbot/config.py (working copy)
|
| -@@ -45,6 +45,7 @@
|
| - category=None,
|
| - nextSlave=None,
|
| - nextBuild=None,
|
| -+ nextSlaveAndBuild=None,
|
| - locks=None,
|
| - env=None,
|
| - properties=None,
|
| -@@ -95,6 +96,7 @@
|
| - self.category = category
|
| - self.nextSlave = nextSlave
|
| - self.nextBuild = nextBuild
|
| -+ self.nextSlaveAndBuild = nextSlaveAndBuild
|
| - self.locks = locks
|
| - self.env = env
|
| - self.properties = properties
|
| -@@ -114,6 +116,8 @@
|
| - rv['nextSlave'] = self.nextSlave
|
| - if self.nextBuild:
|
| - rv['nextBuild'] = self.nextBuild
|
| -+ if self.nextSlaveAndBuild:
|
| -+ rv['nextSlaveAndBuild'] = self.nextSlaveAndBuild
|
| - if self.locks:
|
| - rv['locks'] = self.locks
|
| - if self.env:
|
| -Index: buildbot/process/builder.py
|
| -===================================================================
|
| ---- buildbot/process/builder.py (revision 132861)
|
| -+++ buildbot/process/builder.py (working copy)
|
| -@@ -102,6 +102,14 @@
|
| - self.nextBuild = setup.get('nextBuild')
|
| - if self.nextBuild is not None and not callable(self.nextBuild):
|
| - raise ValueError("nextBuild must be callable")
|
| -+ self.nextSlaveAndBuild = setup.get('nextSlaveAndBuild')
|
| -+ if self.nextSlaveAndBuild is not None:
|
| -+ if not callable(self.nextSlaveAndBuild):
|
| -+ raise ValueError("nextSlaveAndBuild must be callable")
|
| -+ if self.nextBuild or self.nextSlave:
|
| -+ raise ValueError("nextSlaveAndBuild cannot be specified"
|
| -+ " together with either nextSlave or nextBuild")
|
| -+
|
| - self.buildHorizon = setup.get('buildHorizon')
|
| - self.logHorizon = setup.get('logHorizon')
|
| - self.eventHorizon = setup.get('eventHorizon')
|
| -@@ -178,6 +186,8 @@
|
| - diffs.append('nextSlave changed from %s to %s' % (self.nextSlave, setup.get('nextSlave')))
|
| - if setup.get('nextBuild') != self.nextBuild:
|
| - diffs.append('nextBuild changed from %s to %s' % (self.nextBuild, setup.get('nextBuild')))
|
| -+ if setup.get('nextSlaveAndBuild') != self.nextSlaveAndBuild:
|
| -+ diffs.append('nextSlaveAndBuild changed from %s to %s' % (self.nextSlaveAndBuild, setup.get('nextSlaveAndBuild')))
|
| - if setup.get('buildHorizon', None) != self.buildHorizon:
|
| - diffs.append('buildHorizon changed from %s to %s' % (self.buildHorizon, setup['buildHorizon']))
|
| - if setup.get('logHorizon', None) != self.logHorizon:
|
| -@@ -605,6 +615,26 @@
|
| -
|
| - # Build Creation
|
| -
|
| -+ def _checkSlaveBuilder(self, slavebuilder, available_slavebuilders):
|
| -+ if slavebuilder not in available_slavebuilders:
|
| -+ next_func = 'nextSlave'
|
| -+ if self.nextSlaveAndBuild:
|
| -+ next_func = 'nextSlaveAndBuild'
|
| -+ log.msg("%s chose a nonexistent slave for builder '%s'; cannot"
|
| -+ " start build" % (next_func, self.name))
|
| -+ return False
|
| -+ return True
|
| -+
|
| -+ def _checkBrDict(self, brdict, unclaimed_requests):
|
| -+ if brdict not in unclaimed_requests:
|
| -+ next_func = 'nextBuild'
|
| -+ if self.nextSlaveAndBuild:
|
| -+ next_func = 'nextSlaveAndBuild'
|
| -+ log.msg("%s chose a nonexistent request for builder '%s'; cannot"
|
| -+ " start build" % (next_func, self.name))
|
| -+ return False
|
| -+ return True
|
| -+
|
| - @defer.deferredGenerator
|
| - def maybeStartBuild(self):
|
| - # This method is called by the botmaster whenever this builder should
|
| -@@ -645,34 +675,53 @@
|
| -
|
| - # match them up until we're out of options
|
| - while available_slavebuilders and unclaimed_requests:
|
| -- # first, choose a slave (using nextSlave)
|
| -- wfd = defer.waitForDeferred(
|
| -- self._chooseSlave(available_slavebuilders))
|
| -- yield wfd
|
| -- slavebuilder = wfd.getResult()
|
| -+ brdict = None
|
| -+ if self.nextSlaveAndBuild:
|
| -+ # convert brdicts to BuildRequest objects
|
| -+ wfd = defer.waitForDeferred(
|
| -+ defer.gatherResults([self._brdictToBuildRequest(brdict)
|
| -+ for brdict in unclaimed_requests]))
|
| -+ yield wfd
|
| -+ breqs = wfd.getResult()
|
| -
|
| -- if not slavebuilder:
|
| -- break
|
| -+ wfd = defer.waitForDeferred(defer.maybeDeferred(
|
| -+ self.nextSlaveAndBuild,
|
| -+ available_slavebuilders,
|
| -+ breqs))
|
| -+ yield wfd
|
| -+ slavebuilder, br = wfd.getResult()
|
| -
|
| -- if slavebuilder not in available_slavebuilders:
|
| -- log.msg(("nextSlave chose a nonexistent slave for builder "
|
| -- "'%s'; cannot start build") % self.name)
|
| -- break
|
| -+ # Find the corresponding brdict for the returned BuildRequest
|
| -+ if br:
|
| -+ for brdict_i in unclaimed_requests:
|
| -+ if brdict_i['brid'] == br.id:
|
| -+ brdict = brdict_i
|
| -+ break
|
| -
|
| -- # then choose a request (using nextBuild)
|
| -- wfd = defer.waitForDeferred(
|
| -- self._chooseBuild(unclaimed_requests))
|
| -- yield wfd
|
| -- brdict = wfd.getResult()
|
| -+ if (not self._checkSlaveBuilder(slavebuilder,
|
| -+ available_slavebuilders)
|
| -+ or not self._checkBrDict(brdict, unclaimed_requests)):
|
| -+ break
|
| -+ else:
|
| -+ # first, choose a slave (using nextSlave)
|
| -+ wfd = defer.waitForDeferred(
|
| -+ self._chooseSlave(available_slavebuilders))
|
| -+ yield wfd
|
| -+ slavebuilder = wfd.getResult()
|
| -
|
| -- if not brdict:
|
| -- break
|
| -+ if not self._checkSlaveBuilder(slavebuilder,
|
| -+ available_slavebuilders):
|
| -+ break
|
| -
|
| -- if brdict not in unclaimed_requests:
|
| -- log.msg(("nextBuild chose a nonexistent request for builder "
|
| -- "'%s'; cannot start build") % self.name)
|
| -- break
|
| -+ # then choose a request (using nextBuild)
|
| -+ wfd = defer.waitForDeferred(
|
| -+ self._chooseBuild(unclaimed_requests))
|
| -+ yield wfd
|
| -+ brdict = wfd.getResult()
|
| -
|
| -+ if not self._checkBrDict(brdict, unclaimed_requests):
|
| -+ break
|
| -+
|
| - # merge the chosen request with any compatible requests in the
|
| - # queue
|
| - wfd = defer.waitForDeferred(
|
| -
|
| -
|
| -Eliminate circular references between JsonResource and HelpResource.
|
| -
|
| -Index: buildbot/status/web/status_json.py
|
| -===================================================================
|
| ---- buildbot/status/web/status_json.py (revision 132909)
|
| -+++ buildbot/status/web/status_json.py (working copy)
|
| -@@ -131,17 +131,16 @@
|
| - resource.Resource.__init__(self)
|
| - # buildbot.status.builder.Status
|
| - self.status = status
|
| -- if self.help:
|
| -- pageTitle = ''
|
| -- if self.pageTitle:
|
| -- pageTitle = self.pageTitle + ' help'
|
| -- self.putChild('help',
|
| -- HelpResource(self.help, pageTitle=pageTitle, parent_node=self))
|
| -
|
| - def getChildWithDefault(self, path, request):
|
| - """Adds transparent support for url ending with /"""
|
| - if path == "" and len(request.postpath) == 0:
|
| - return self
|
| -+ if (path == "help" or path == "help/") and self.help:
|
| -+ pageTitle = ''
|
| -+ if self.pageTitle:
|
| -+ pageTitle = self.pageTitle + ' help'
|
| -+ return HelpResource(self.help, pageTitle=pageTitle, parent_node=self)
|
| - # Equivalent to resource.Resource.getChildWithDefault()
|
| - if self.children.has_key(path):
|
| - return self.children[path]
|
| -@@ -340,12 +339,13 @@
|
| - HtmlResource.__init__(self)
|
| - self.text = text
|
| - self.pageTitle = pageTitle
|
| -- self.parent_node = parent_node
|
| -+ self.parent_level = parent_node.level
|
| -+ self.parent_children = parent_node.children.keys()
|
| -
|
| - def content(self, request, cxt):
|
| -- cxt['level'] = self.parent_node.level
|
| -+ cxt['level'] = self.parent_level
|
| - cxt['text'] = ToHtml(self.text)
|
| -- cxt['children'] = [ n for n in self.parent_node.children.keys() if n != 'help' ]
|
| -+ cxt['children'] = [ n for n in self.parent_children if n != 'help' ]
|
| - cxt['flags'] = ToHtml(FLAGS)
|
| - cxt['examples'] = ToHtml(EXAMPLES).replace(
|
| - 'href="/json',
|
| -
|
| -
|
| -Don't cache json build pages; that defeats the purpose of
|
| -buildbot.status.builder.buildCache by holding references to all
|
| -builds.
|
| -
|
| -Index: buildbot/status/web/status_json.py
|
| -===================================================================
|
| ---- buildbot/status/web/status_json.py (revision 133407)
|
| -+++ buildbot/status/web/status_json.py (working copy)
|
| -@@ -451,24 +451,16 @@
|
| - if isinstance(path, int) or _IS_INT.match(path):
|
| - build_status = self.builder_status.getBuild(int(path))
|
| - if build_status:
|
| -- build_status_number = str(build_status.getNumber())
|
| -- # Happens with negative numbers.
|
| -- child = self.children.get(build_status_number)
|
| -- if child:
|
| -- return child
|
| -- # Create it on-demand.
|
| -- child = BuildJsonResource(self.status, build_status)
|
| -- # Cache it. Never cache negative numbers.
|
| -- # TODO(maruel): Cleanup the cache once it's too heavy!
|
| -- self.putChild(build_status_number, child)
|
| -- return child
|
| -+ # Don't cache BuildJsonResource; that would defeat the cache-ing
|
| -+ # mechanism in place for BuildStatus objects (in BuilderStatus).
|
| -+ return BuildJsonResource(self.status, build_status)
|
| - return JsonResource.getChild(self, path, request)
|
| -
|
| - def asDict(self, request):
|
| - results = {}
|
| -- # If max > buildCacheSize, it'll trash the cache...
|
| -+ # If max is too big, it'll trash the cache...
|
| - max = int(RequestArg(request, 'max',
|
| -- self.builder_status.buildCacheSize))
|
| -+ self.builder_status.buildCacheSize/2))
|
| - for i in range(0, max):
|
| - child = self.getChildWithDefault(-i, request)
|
| - if not isinstance(child, BuildJsonResource):
|
| -
|
| -
|
| -
|
| -Fix getLastFinishedBuild() so it works correctly when there are 2 or more
|
| -running builds for a given builder. Previously, it would always return the
|
| -second-to-last build if the first was missing or running.
|
| -
|
| -Index: buildbot/status/builder.py
|
| -===================================================================
|
| ---- buildbot/status/builder.py
|
| -+++ buildbot/status/builder.py
|
| -@@ -313,10 +313,12 @@ class BuilderStatus(styles.Versioned):
|
| - return self.currentBuilds
|
| -
|
| - def getLastFinishedBuild(self):
|
| - - b = self.getBuild(-1)
|
| - - if not (b and b.isFinished()):
|
| - - b = self.getBuild(-2)
|
| - - return b
|
| - + for build in self.generateFinishedBuilds(num_builds=1):
|
| - + assert build and build.isFinished, \
|
| - + 'builder %s build %s is not finished' % (
|
| - + self.getName(), build)
|
| - + return build
|
| - + return None
|
| -
|
| - def getCategory(self):
|
| - return self.category
|
| -
|
| -Fix bug introduced in previous fix to status_json.py (line 2168 in this file).
|
| -
|
| -Index: buildbot/status/web/status_json.py
|
| -===================================================================
|
| ---- buildbot/status/web/status_json.py (revision 147894)
|
| -+++ buildbot/status/web/status_json.py (working copy)
|
| -@@ -140,7 +140,9 @@
|
| - pageTitle = ''
|
| - if self.pageTitle:
|
| - pageTitle = self.pageTitle + ' help'
|
| -- return HelpResource(self.help, pageTitle=pageTitle, parent_node=self)
|
| -+ res = HelpResource(self.help, pageTitle=pageTitle, parent_node=self)
|
| -+ res.level = self.level + 1
|
| -+ return res
|
| - # Equivalent to resource.Resource.getChildWithDefault()
|
| - if self.children.has_key(path):
|
| - return self.children[path]
|
| -
|
| -
|
| -Make buildbot names in urls from console more readable.
|
| -
|
| -Index: buildbot/status/web/console.py
|
| -===================================================================
|
| ---- a/scripts/master/chromium_status_bb8.py
|
| -+++ b/scripts/master/chromium_status_bb8.py
|
| -@@ -141,7 +141,7 @@ class HorizontalOneBoxPerBuilder(base.HtmlResource):
|
| - title = builder_name
|
| - show_name = 'off' not in request.args.get('titles', ['off'])
|
| - url = (base.path_to_root(request) + "waterfall?builder=" +
|
| -- urllib.quote(builder_name, safe=''))
|
| -+ urllib.quote(builder_name, safe='() '))
|
| - cxt_builders.append({'outcome': classname,
|
| - 'name': title,
|
| - 'url': url,
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -index e70b72d..5bf2725 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -@@ -96,7 +96,7 @@ class GitPoller(base.PollingChangeSource):
|
| - def git_init(_):
|
| - log.msg('gitpoller: initializing working dir from %s' % self.repourl)
|
| - d = utils.getProcessOutputAndValue(self.gitbin,
|
| -- ['init', self.workdir], env=dict(PATH=os.environ['PATH']))
|
| -+ ['init', self.workdir], env=os.environ)
|
| - d.addCallback(self._convert_nonzero_to_failure)
|
| - d.addErrback(self._stop_on_failure)
|
| - return d
|
| -@@ -105,7 +105,7 @@ class GitPoller(base.PollingChangeSource):
|
| - def git_remote_add(_):
|
| - d = utils.getProcessOutputAndValue(self.gitbin,
|
| - ['remote', 'add', 'origin', self.repourl],
|
| -- path=self.workdir, env=dict(PATH=os.environ['PATH']))
|
| -+ path=self.workdir, env=os.environ)
|
| - d.addCallback(self._convert_nonzero_to_failure)
|
| - d.addErrback(self._stop_on_failure)
|
| - return d
|
| -@@ -115,7 +115,7 @@ class GitPoller(base.PollingChangeSource):
|
| - args = ['fetch', 'origin']
|
| - self._extend_with_fetch_refspec(args)
|
| - d = utils.getProcessOutputAndValue(self.gitbin, args,
|
| -- path=self.workdir, env=dict(PATH=os.environ['PATH']))
|
| -+ path=self.workdir, env=os.environ)
|
| - d.addCallback(self._convert_nonzero_to_failure)
|
| - d.addErrback(self._stop_on_failure)
|
| - return d
|
| -@@ -126,11 +126,11 @@ class GitPoller(base.PollingChangeSource):
|
| - if self.branch == 'master': # repo is already on branch 'master', so reset
|
| - d = utils.getProcessOutputAndValue(self.gitbin,
|
| - ['reset', '--hard', 'origin/%s' % self.branch],
|
| -- path=self.workdir, env=dict(PATH=os.environ['PATH']))
|
| -+ path=self.workdir, env=os.environ)
|
| - else:
|
| - d = utils.getProcessOutputAndValue(self.gitbin,
|
| - ['checkout', '-b', self.branch, 'origin/%s' % self.branch],
|
| -- path=self.workdir, env=dict(PATH=os.environ['PATH']))
|
| -+ path=self.workdir, env=os.environ)
|
| - d.addCallback(self._convert_nonzero_to_failure)
|
| - d.addErrback(self._stop_on_failure)
|
| - return d
|
| -@@ -169,7 +169,7 @@ class GitPoller(base.PollingChangeSource):
|
| -
|
| - def _get_commit_comments(self, rev):
|
| - args = ['log', rev, '--no-walk', r'--format=%s%n%b']
|
| -- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
|
| -+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
|
| - def process(git_output):
|
| - stripped_output = git_output.strip().decode(self.encoding)
|
| - if len(stripped_output) == 0:
|
| -@@ -181,7 +181,7 @@ class GitPoller(base.PollingChangeSource):
|
| - def _get_commit_timestamp(self, rev):
|
| - # unix timestamp
|
| - args = ['log', rev, '--no-walk', r'--format=%ct']
|
| -- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
|
| -+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
|
| - def process(git_output):
|
| - stripped_output = git_output.strip()
|
| - if self.usetimestamps:
|
| -@@ -198,7 +198,7 @@ class GitPoller(base.PollingChangeSource):
|
| -
|
| - def _get_commit_files(self, rev):
|
| - args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
|
| -- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
|
| -+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
|
| - def process(git_output):
|
| - fileList = git_output.split()
|
| - return fileList
|
| -@@ -207,7 +207,7 @@ class GitPoller(base.PollingChangeSource):
|
| -
|
| - def _get_commit_name(self, rev):
|
| - args = ['log', rev, '--no-walk', r'--format=%aE']
|
| -- d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']), errortoo=False )
|
| -+ d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
|
| - def process(git_output):
|
| - stripped_output = git_output.strip().decode(self.encoding)
|
| - if len(stripped_output) == 0:
|
| -@@ -231,7 +231,7 @@ class GitPoller(base.PollingChangeSource):
|
| - # deferred will not use the response.
|
| - d = utils.getProcessOutput(self.gitbin, args,
|
| - path=self.workdir,
|
| -- env=dict(PATH=os.environ['PATH']), errortoo=True )
|
| -+ env=os.environ, errortoo=True )
|
| -
|
| - return d
|
| -
|
| -@@ -241,7 +241,7 @@ class GitPoller(base.PollingChangeSource):
|
| - revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'--format=%H']
|
| - self.changeCount = 0
|
| - d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir,
|
| -- env=dict(PATH=os.environ['PATH']), errortoo=False )
|
| -+ env=os.environ, errortoo=False )
|
| - wfd = defer.waitForDeferred(d)
|
| - yield wfd
|
| - results = wfd.getResult()
|
| -@@ -307,7 +307,7 @@ class GitPoller(base.PollingChangeSource):
|
| - return
|
| - log.msg('gitpoller: catching up tracking branch')
|
| - args = ['reset', '--hard', 'origin/%s' % (self.branch,)]
|
| -- d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=dict(PATH=os.environ['PATH']))
|
| -+ d = utils.getProcessOutputAndValue(self.gitbin, args, path=self.workdir, env=os.environ)
|
| - d.addCallback(self._convert_nonzero_to_failure)
|
| - return d
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py
|
| -index 9a47d41..c5e1960 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_changes_gitpoller.py
|
| -@@ -13,6 +13,7 @@
|
| - #
|
| - # Copyright Buildbot Team Members
|
| -
|
| -+import os
|
| - from twisted.trial import unittest
|
| - from twisted.internet import defer
|
| - from exceptions import Exception
|
| -@@ -20,6 +21,9 @@ from buildbot.changes import gitpoller
|
| - from buildbot.test.util import changesource, gpo
|
| - from buildbot.util import epoch2datetime
|
| -
|
| -+# Test that environment variables get propagated to subprocesses (See #2116)
|
| -+os.environ['TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES'] = 'TRUE'
|
| -+
|
| - class GitOutputParsing(gpo.GetProcessOutputMixin, unittest.TestCase):
|
| - """Test GitPoller methods for parsing git output"""
|
| - def setUp(self):
|
| -@@ -120,6 +124,11 @@ class TestGitPoller(gpo.GetProcessOutputMixin,
|
| - self.assertSubstring("GitPoller", self.poller.describe())
|
| -
|
| - def test_poll(self):
|
| -+ # Test that environment variables get propagated to subprocesses (See #2116)
|
| -+ os.putenv('TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES', 'TRUE')
|
| -+ self.addGetProcessOutputExpectEnv({'TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES': 'TRUE'})
|
| -+ self.addGetProcessOutputAndValueExpectEnv({'TEST_THAT_ENVIRONMENT_GETS_PASSED_TO_SUBPROCESSES': 'TRUE'})
|
| -+
|
| - # patch out getProcessOutput and getProcessOutputAndValue for the
|
| - # benefit of the _get_changes method
|
| - self.addGetProcessOutputResult(
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py b/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py
|
| -index a23bdb2..c9daf0c 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/test/util/gpo.py
|
| -@@ -39,6 +39,8 @@ class GetProcessOutputMixin:
|
| - self._gpo_patched = False
|
| - self._gpo_patterns = []
|
| - self._gpoav_patterns = []
|
| -+ self._gpo_expect_env = {}
|
| -+ self._gpoav_expect_env = {}
|
| -
|
| - def tearDownGetProcessOutput(self):
|
| - pass
|
| -@@ -54,11 +56,23 @@ class GetProcessOutputMixin:
|
| -
|
| - # these can be overridden if necessary
|
| - def patched_getProcessOutput(self, bin, args, env=None, **kwargs):
|
| -+ for var, value in self._gpo_expect_env.items():
|
| -+ if env.get(var) != value:
|
| -+ self._flag_error('Expected environment to have %s = %r' % (var, value))
|
| -+
|
| - return self._patched(self._gpo_patterns, bin, args, env=env, **kwargs)
|
| -
|
| - def patched_getProcessOutputAndValue(self, bin, args, env=None, **kwargs):
|
| -+ for var, value in self._gpoav_expect_env.items():
|
| -+ if env.get(var) != value:
|
| -+ self._flag_error('Expected environment to have %s = %r' % (var, value))
|
| -+
|
| - return self._patched(self._gpoav_patterns, bin, args, env=env, **kwargs)
|
| -
|
| -+ def _flag_error(self, msg):
|
| -+ # print msg
|
| -+ assert False, msg
|
| -+
|
| - def _patch_gpo(self):
|
| - if not self._gpo_patched:
|
| - self.patch(utils, "getProcessOutput",
|
| -@@ -67,6 +81,12 @@ class GetProcessOutputMixin:
|
| - self.patched_getProcessOutputAndValue)
|
| - self._gpo_patched = True
|
| -
|
| -+ def addGetProcessOutputExpectEnv(self, d):
|
| -+ self._gpo_expect_env.update(d)
|
| -+
|
| -+ def addGetProcessOutputAndValueExpectEnv(self, d):
|
| -+ self._gpoav_expect_env.update(d)
|
| -+
|
| - def addGetProcessOutputResult(self, pattern, result):
|
| - self._patch_gpo()
|
| - self._gpo_patterns.append((pattern, result))
|
| -
|
| -
|
| -
|
| -
|
| -Correctly escape the committer in the waterfall/help page.
|
| -
|
| -Index: waterfallhelp.html
|
| -===================================================================
|
| ---- waterfallhelp.html (revision 164735)
|
| -+++ waterfallhelp.html (working copy)
|
| -@@ -99,7 +99,7 @@
|
| - {% for cn in committers %}
|
| - <tr>
|
| - <td>
|
| -- Show Committer: <input type="text" name="committer" value="{{ cn }}">
|
| -+ Show Committer: <input type="text" name="committer" value="{{ cn|e }}">
|
| - </td>
|
| - </tr>
|
| - {% endfor %}
|
| -
|
| -
|
| -commit 1fe8fbcf99cd98546a98d2dd6a3be1206ea590c7
|
| -Author: Peter Mayo <petermayo@chromium.org>
|
| -Date: Fri Nov 23 12:37:00 2012 -0500
|
| -
|
| - Optionally add project to changes in waterfall display.
|
| -
|
| - In some waterfalls it is useful to separate different change
|
| - sources visually. This allows an attribute to reflect that to
|
| - be passed though from config to template.
|
| -
|
| - Related reviews: https://codereview.chromium.org/11425002
|
| -
|
| -diff --git a/master/buildbot/status/web/changes.py b/master/buildbot/status/web/changes.py
|
| -index 0971bea..e579669 100644
|
| ---- a/master/buildbot/status/web/changes.py
|
| -+++ b/master/buildbot/status/web/changes.py
|
| -@@ -64,7 +64,8 @@ class ChangeBox(components.Adapter):
|
| - text = template.module.box_contents(url=url,
|
| - who=self.original.getShortAuthor(),
|
| - pageTitle=self.original.comments,
|
| -- revision=self.original.revision)
|
| -+ revision=self.original.revision,
|
| -+ project=self.original.project)
|
| - return Box([text], class_="Change")
|
| - components.registerAdapter(ChangeBox, Change, IBox)
|
| -
|
| -diff --git a/master/buildbot/status/web/templates/change_macros.html b/master/buildbot/status/web/templates/change_macros.html
|
| -index dc6a9b2..2ca01d9 100644
|
| ---- a/master/buildbot/status/web/templates/change_macros.html
|
| -+++ b/master/buildbot/status/web/templates/change_macros.html
|
| -@@ -71,6 +71,6 @@
|
| - {% endif %}
|
| - {%- endmacro %}
|
| -
|
| --{% macro box_contents(who, url, pageTitle, revision) -%}
|
| -+{% macro box_contents(who, url, pageTitle, revision, project) -%}
|
| - <a href="{{ url }}" title="{{ pageTitle|e }}">{{ who|user }}</a>
|
| - {%- endmacro %}
|
| -
|
| -
|
| -
|
| -Disable an assert that is causing internal inconsistent state because of
|
| -out-of-order build processing.
|
| -
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/builder.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/builder.py
|
| -@@ -502,7 +502,8 @@ class BuilderStatus(styles.Versioned):
|
| - Steps, its ETA, etc), so it is safe to notify our watchers."""
|
| -
|
| - assert s.builder is self # paranoia
|
| -- assert s.number == self.nextBuildNumber - 1
|
| -+ # They can happen out of order.
|
| -+ #assert s.number == self.nextBuildNumber - 1
|
| - assert s not in self.currentBuilds
|
| - self.currentBuilds.append(s)
|
| - self.buildCache.put(s.number, s)
|
| -
|
| -Change buildbot to use the epoll reactor instead of the default select()
|
| -reaction. Breaks compatability with non linux26 systems.
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index 28e46de..7ec0032 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -19,6 +19,16 @@ import signal
|
| - import time
|
| - import textwrap
|
| -
|
| -+try:
|
| -+ import pyximport
|
| -+ pyximport.install()
|
| -+ from twisted.internet import epollreactor
|
| -+ epollreactor.install()
|
| -+except ImportError:
|
| -+ print 'Unable to load the epoll module, falling back to select.'
|
| -+ print 'This may be caused by the lack of cython, python-dev, or'
|
| -+ print 'you may be on a platform other than linux 2.6'
|
| -+
|
| - from zope.interface import implements
|
| - from twisted.python import log, components
|
| - from twisted.internet import defer, reactor
|
| -
|
| -
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -@@ -110,7 +110,7 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin):
|
| - 'num_changes' : len(changes),
|
| - })
|
| -
|
| -- numbuilds = int(req.args.get('numbuilds', ['5'])[0])
|
| -+ numbuilds = int(req.args.get('numbuilds', ['20'])[0])
|
| - recent = cxt['recent'] = []
|
| - for build in b.generateFinishedBuilds(num_builds=int(numbuilds)):
|
| - recent.append(self.get_line_values(req, build, False))
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index 7ec0032..425572e 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -295,7 +295,7 @@ class BuildMaster(service.MultiService):
|
| - "logHorizon", "buildHorizon", "changeHorizon",
|
| - "logMaxSize", "logMaxTailSize", "logCompressionMethod",
|
| - "db_url", "multiMaster", "db_poll_interval",
|
| -- "metrics", "caches"
|
| -+ "metrics", "caches", "autoBuildCacheRatio"
|
| - )
|
| - for k in config.keys():
|
| - if k not in known_keys:
|
| -@@ -357,6 +357,7 @@ class BuildMaster(service.MultiService):
|
| -
|
| - metrics_config = config.get("metrics")
|
| - caches_config = config.get("caches", {})
|
| -+ autoBuildCacheRatio = config.get("autoBuildCacheRatio", None)
|
| -
|
| - except KeyError:
|
| - log.msg("config dictionary is missing a required parameter")
|
| -@@ -555,6 +556,7 @@ class BuildMaster(service.MultiService):
|
| - self.logHorizon = logHorizon
|
| - self.buildHorizon = buildHorizon
|
| - self.slavePortnum = slavePortnum # TODO: move this to master.config.slavePortnum
|
| -+ self.autoBuildCacheRatio = autoBuildCacheRatio
|
| -
|
| - # Set up the database
|
| - d.addCallback(lambda res:
|
| -@@ -784,6 +786,16 @@ class BuildMaster(service.MultiService):
|
| - for builder in allBuilders.values():
|
| - builder.builder_status.reconfigFromBuildmaster(self)
|
| -
|
| -+ # Adjust the caches if autoBuildCacheRatio is on. Each builder's
|
| -+ # build cache is set to (autoBuildCacheRatio * number of slaves).
|
| -+ # This assumes that each slave-entry can only execute one concurrent
|
| -+ # build.
|
| -+ if self.autoBuildCacheRatio:
|
| -+ builder_status = builder.builder_status
|
| -+ slavecount = len(builder_status.slavenames)
|
| -+ max_size = max(self.autoBuildCacheRatio * slavecount, 15)
|
| -+ builder_status.buildCache.set_max_size(max_size)
|
| -+ builder_status.buildCacheSize = max_size
|
| -+
|
| - metrics.MetricCountEvent.log("num_builders",
|
| - len(allBuilders), absolute=True)
|
| -
|
| -
|
| -Added error catching so buildbot would not crash during initial config.
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index df80ca6..5304401 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -18,6 +18,7 @@ import os
|
| - import signal
|
| - import time
|
| - import textwrap
|
| -+import twisted.internet.error
|
| -
|
| - try:
|
| - import pyximport
|
| -@@ -28,6 +29,8 @@ except ImportError:
|
| - print 'Unable to load the epoll module, falling back to select.'
|
| - print 'This may be caused by the lack of cython, python-dev, or'
|
| - print 'you may be on a platform other than linux 2.6'
|
| -+except twisted.internet.error.ReactorAlreadyInstalledError:
|
| -+ pass
|
| -
|
| - from zope.interface import implements
|
| - from twisted.python import log, components
|
| -
|
| -Suppress a warning about old name.
|
| -Index: buildbot/changes/svnpoller.py
|
| -===================================================================
|
| ---- buildbot/changes/svnpoller.py (revision 195053)
|
| -+++ buildbot/changes/svnpoller.py (working copy)
|
| -@@ -352,7 +352,7 @@
|
| - log.msg("Ignoring deletion of branch '%s'" % branch)
|
| - else:
|
| - chdict = dict(
|
| -- who=author,
|
| -+ author=author,
|
| - files=files,
|
| - comments=comments,
|
| - revision=revision,
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -index ed12ea0..32e1bd3 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -@@ -635,8 +635,8 @@ class BuildStep:
|
| - doStepIf = True
|
| - hideStepIf = False
|
| - # like doStepIf, but evaluated at runtime if executing under runbuild.py
|
| -- # we also overload 'False' to signify this isn't a buildrunner step
|
| -- brDoStepIf = False
|
| -+ # We use None to signify that a step is not a buildrunner step.
|
| -+ brDoStepIf = None
|
| -
|
| - def __init__(self, **kwargs):
|
| - self.factory = (self.__class__, dict(kwargs))
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index b656563..e48542d 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -16,6 +16,7 @@
|
| -
|
| - """Simple JSON exporter."""
|
| -
|
| -+import collections
|
| - import datetime
|
| - import os
|
| - import re
|
| -@@ -635,21 +636,38 @@ class SlaveJsonResource(JsonResource):
|
| - self.builders.append(builderName)
|
| - return self.builders
|
| -
|
| -+ def getSlaveBuildMap(self, buildcache, buildercache):
|
| -+ for builderName in self.getBuilders():
|
| -+ if builderName not in buildercache:
|
| -+ buildercache.add(builderName)
|
| -+ builder_status = self.status.getBuilder(builderName)
|
| -+ for i in range(1, builder_status.buildCacheSize - 1):
|
| -+ build_status = builder_status.getBuild(-i)
|
| -+ if not build_status or not build_status.isFinished():
|
| -+ # If not finished, it will appear in runningBuilds.
|
| -+ break
|
| -+ slave = buildcache[build_status.getSlavename()]
|
| -+ slave.setdefault(builderName, []).append(
|
| -+ build_status.getNumber())
|
| -+ return buildcache[self.name]
|
| -+
|
| - def asDict(self, request):
|
| -+ if not hasattr(request, 'custom_data'):
|
| -+ request.custom_data = {}
|
| -+ if 'buildcache' not in request.custom_data:
|
| -+ # buildcache is used to cache build information across multiple
|
| -+ # invocations of SlaveJsonResource. It should be set to an empty
|
| -+ # collections.defaultdict(dict).
|
| -+ request.custom_data['buildcache'] = collections.defaultdict(dict)
|
| -+
|
| -+ # Tracks which builders have been stored in the buildcache.
|
| -+ request.custom_data['buildercache'] = set()
|
| -+
|
| - results = self.slave_status.asDict()
|
| -- # Enhance it by adding more informations.
|
| -- results['builders'] = {}
|
| -- for builderName in self.getBuilders():
|
| -- builds = []
|
| -- builder_status = self.status.getBuilder(builderName)
|
| -- for i in range(1, builder_status.buildCacheSize - 1):
|
| -- build_status = builder_status.getBuild(-i)
|
| -- if not build_status or not build_status.isFinished():
|
| -- # If not finished, it will appear in runningBuilds.
|
| -- break
|
| -- if build_status.getSlavename() == self.name:
|
| -- builds.append(build_status.getNumber())
|
| -- results['builders'][builderName] = builds
|
| -+ # Enhance it by adding more information.
|
| -+ results['builders'] = self.getSlaveBuildMap(
|
| -+ request.custom_data['buildcache'],
|
| -+ request.custom_data['buildercache'])
|
| - return results
|
| -
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/builder.py b/third_party/buildbot_8_4p1/buildbot/status/builder.py
|
| -index 3f42eb6..c5bd1ec 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/builder.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/builder.py
|
| -@@ -17,6 +17,7 @@
|
| - import weakref
|
| - import gc
|
| - import os, re, itertools
|
| -+import random
|
| - from cPickle import load, dump
|
| -
|
| - from zope.interface import implements
|
| -@@ -323,17 +324,58 @@ class BuilderStatus(styles.Versioned):
|
| - def getCategory(self):
|
| - return self.category
|
| -
|
| -- def getBuild(self, number):
|
| -+ def _resolveBuildNumber(self, number):
|
| - if number < 0:
|
| - number = self.nextBuildNumber + number
|
| - if number < 0 or number >= self.nextBuildNumber:
|
| - return None
|
| -+ return number
|
| -
|
| -+ def _safeGetBuild(self, build_number):
|
| - try:
|
| -- return self.getBuildByNumber(number)
|
| -+ return self.getBuildByNumber(build_number)
|
| - except IndexError:
|
| - return None
|
| -
|
| -+ def getBuild(self, number):
|
| -+ number = self._resolveBuildNumber(number)
|
| -+
|
| -+ if number is None:
|
| -+ return None
|
| -+
|
| -+ return self._safeGetBuild(number)
|
| -+
|
| -+ def getBuilds(self, numbers):
|
| -+ """Cache-aware method to get multiple builds.
|
| -+
|
| -+ Prevents cascading evict/load when multiple builds are requested in
|
| -+ succession: requesting build 1 evicts build 2, requesting build 2 evicts
|
| -+ build 3, etc.
|
| -+
|
| -+ We query the buildCache and load hits first, then misses. When loading,
|
| -+ we randomize the load order to alleviate the problem when external web
|
| -+ requests load builds sequentially (they don't have access to this
|
| -+ function).
|
| -+ """
|
| -+
|
| -+ numbers = list(enumerate(self._resolveBuildNumber(x) for x in numbers))
|
| -+ random.shuffle(numbers)
|
| -+
|
| -+ builds = [None] * len(numbers)
|
| -+ misses = []
|
| -+ for idx, build_number in numbers:
|
| -+ if build_number is None:
|
| -+ continue
|
| -+ if build_number in self.buildCache.cache:
|
| -+ builds[idx] = self._safeGetBuild(build_number)
|
| -+ else:
|
| -+ misses.append((idx, build_number))
|
| -+
|
| -+ for idx, build_number in misses:
|
| -+ builds[idx] = self._safeGetBuild(build_number)
|
| -+
|
| -+ return builds
|
| -+
|
| - def getEvent(self, number):
|
| - try:
|
| - return self.events[number]
|
| -@@ -616,6 +658,11 @@ class BuilderStatus(styles.Versioned):
|
| - # Collect build numbers.
|
| - # Important: Only grab the *cached* builds numbers to reduce I/O.
|
| - current_builds = [b.getNumber() for b in self.currentBuilds]
|
| -+
|
| -+ # Populates buildCache with last N builds.
|
| -+ buildnums = range(-1, -(self.buildCacheSize - 1), -1)
|
| -+ self.getBuilds(buildnums)
|
| -+
|
| - cached_builds = list(set(self.buildCache.cache.keys() + current_builds))
|
| - cached_builds.sort()
|
| - result['cachedBuilds'] = cached_builds
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index c357939..e7cd932 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -641,8 +641,11 @@ class SlaveJsonResource(JsonResource):
|
| - if builderName not in buildercache:
|
| - buildercache.add(builderName)
|
| - builder_status = self.status.getBuilder(builderName)
|
| -- for i in range(1, builder_status.buildCacheSize - 1):
|
| -- build_status = builder_status.getBuild(-i)
|
| -+
|
| -+ buildnums = range(-1, -(builder_status.buildCacheSize - 1), -1)
|
| -+ builds = builder_status.getBuilds(buildnums)
|
| -+
|
| -+ for build_status in builds:
|
| - if not build_status or not build_status.isFinished():
|
| - # If not finished, it will appear in runningBuilds.
|
| - break
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
|
| -index 264b599..f64e0b9 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/buildstep.py
|
| -@@ -13,7 +13,10 @@
|
| - #
|
| - # Copyright Buildbot Team Members
|
| -
|
| -+import collections
|
| - import os, weakref
|
| -+
|
| -+
|
| - from zope.interface import implements
|
| - from twisted.persisted import styles
|
| - from twisted.python import log
|
| -@@ -21,6 +24,9 @@ from twisted.internet import reactor, defer
|
| - from buildbot import interfaces, util
|
| - from buildbot.status.logfile import LogFile, HTMLLogFile
|
| -
|
| -+# This allows use of OrderedDict on python 2.6, found in scripts/common.
|
| -+import common.python26_polyfill # pylint: disable=W0611
|
| -+
|
| - class BuildStepStatus(styles.Versioned):
|
| - """
|
| - I represent a collection of output status for a
|
| -@@ -70,7 +76,7 @@ class BuildStepStatus(styles.Versioned):
|
| - self.step_number = step_number
|
| - self.hidden = False
|
| - self.logs = []
|
| -- self.urls = {}
|
| -+ self.urls = collections.OrderedDict()
|
| - self.watchers = []
|
| - self.updates = {}
|
| - self.finishedWatchers = []
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -index 5bf2725..71a0386 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -@@ -238,7 +238,8 @@ class GitPoller(base.PollingChangeSource):
|
| - @defer.deferredGenerator
|
| - def _process_changes(self, unused_output):
|
| - # get the change list
|
| -- revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch), r'--format=%H']
|
| -+ revListArgs = ['log', '%s..origin/%s' % (self.branch, self.branch),
|
| -+ r'--format=%H', '--first-parent']
|
| - self.changeCount = 0
|
| - d = utils.getProcessOutput(self.gitbin, revListArgs, path=self.workdir,
|
| - env=os.environ, errortoo=False )
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index 5304401..c90ad32 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -796,7 +796,7 @@ class BuildMaster(service.MultiService):
|
| - if self.autoBuildCacheRatio:
|
| - builder_status = builder.builder_status
|
| - slavecount = len(builder_status.slavenames)
|
| -- max_size = max(self.autoBuildCacheRatio * slavecount, 15)
|
| -+ max_size = max(self.autoBuildCacheRatio * slavecount, 30)
|
| - builder_status.buildCache.set_max_size(max_size)
|
| - builder_status.buildCacheSize = max_size
|
| -
|
| -
|
| -*** maruel on 2013-12-05
|
| -Add support for transferring 'reason' from a build to its triggered build.
|
| -Otherwise the default is 'Triggerable(%(name))' which is not very useful.
|
| -
|
| ---- a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
|
| -@@ -28,7 +28,7 @@ class Triggerable(base.BaseScheduler):
|
| - self._bsc_subscription = None
|
| - self.reason = "Triggerable(%s)" % name
|
| -
|
| -- def trigger(self, ssid, set_props=None):
|
| -+ def trigger(self, ssid, set_props=None, reason=None):
|
| - """Trigger this scheduler with the given sourcestamp ID. Returns a
|
| - deferred that will fire when the buildset is finished."""
|
| - # properties for this buildset are composed of our own properties,
|
| -@@ -42,10 +42,10 @@ class Triggerable(base.BaseScheduler):
|
| - # the duration of interest to the caller is bounded by the lifetime of
|
| - # this process.
|
| - if ssid:
|
| -- d = self.addBuildsetForSourceStamp(reason=self.reason, ssid=ssid,
|
| -+ d = self.addBuildsetForSourceStamp(reason=reason or self.reason, ssid=ssid,
|
| - properties=props)
|
| - else:
|
| -- d = self.addBuildsetForLatest(reason=self.reason, properties=props)
|
| -+ d = self.addBuildsetForLatest(reason=reason or self.reason, properties=props)
|
| - def setup_waiter((bsid,brids)):
|
| - self._waiters[bsid] = d = defer.Deferred()
|
| - self._updateWaiters()
|
| ---- a/third_party/buildbot_8_4p1/buildbot/steps/trigger.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/steps/trigger.py
|
| -@@ -167,7 +167,7 @@ class Trigger(LoggingBuildStep):
|
| - dl = []
|
| - for scheduler in triggered_schedulers:
|
| - sch = all_schedulers[scheduler]
|
| -- dl.append(sch.trigger(ssid, set_props=props_to_set))
|
| -+ dl.append(sch.trigger(ssid, set_props=props_to_set, reason=self.build.reason))
|
| - self.step_status.setText(['triggered'] + triggered_schedulers)
|
| -
|
| - d = defer.DeferredList(dl, consumeErrors=1)
|
| -
|
| -Hard-disables shutdown hooks for https://code.google.com/p/chromium/issues/detail?id=309046
|
| -===================================================================
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/root.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/root.py
|
| -@@ -25,17 +25,9 @@
|
| - status = self.getStatus(request)
|
| -
|
| - if request.path == '/shutdown':
|
| -- if self.getAuthz(request).actionAllowed("cleanShutdown", request):
|
| -- eventually(status.cleanShutdown)
|
| -- return redirectTo("/", request)
|
| -- else:
|
| -- return redirectTo(path_to_authfail(request), request)
|
| -+ return redirectTo(path_to_authfail(request), request)
|
| - elif request.path == '/cancel_shutdown':
|
| -- if self.getAuthz(request).actionAllowed("cleanShutdown", request):
|
| -- eventually(status.cancelCleanShutdown)
|
| -- return redirectTo("/", request)
|
| -- else:
|
| -- return redirectTo(path_to_authfail(request), request)
|
| -+ return redirectTo(path_to_authfail(request), request)
|
| -
|
| - cxt.update(
|
| - shutting_down = status.shuttingDown,
|
| -
|
| -
|
| -Provide support for messageFormatter to create custom subject lines.
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -@@ -587,7 +587,12 @@ class MailNotifier(base.StatusReceiverMultiService):
|
| - build=build, results=build.results)
|
| - msgdict['body'] += tmp['body']
|
| - msgdict['body'] += '\n\n'
|
| -+ # This is terrible. Whichever build is iterated over last will
|
| -+ # overwrite the subject and type set by all previous builds.
|
| -+ # But we never iterate over more than one build, so we'll
|
| -+ # just do this anyway and try not to lose any sleep over it.
|
| - msgdict['type'] = tmp['type']
|
| -+ if 'subject' in tmp:
|
| -+ msgdict['subject'] = tmp['subject']
|
| -
|
| - m = self.createEmail(msgdict, name, self.master_status.getTitle(),
|
| - results, builds, patches, logs)
|
| -
|
| -
|
| -Change minimum cache size to 50 to prevent waterfall and console view bustage.
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -796,7 +796,7 @@ class BuildMaster(service.MultiService):
|
| - if self.autoBuildCacheRatio:
|
| - builder_status = builder.builder_status
|
| - slavecount = len(builder_status.slavenames)
|
| -- max_size = max(self.autoBuildCacheRatio * slavecount, 30)
|
| -+ max_size = max(self.autoBuildCacheRatio * slavecount, 50)
|
| - builder_status.buildCache.set_max_size(max_size)
|
| - builder_status.buildCacheSize = max_size
|
| -
|
| -
|
| -Cherry-pick ANSI color codes in logs from upstream buildbot. These commits were manually
|
| -resolved.
|
| -
|
| - ** https://github.com/buildbot/buildbot/commit/3a563189374737aa2ebf3f6467bf461bc3ec5443
|
| -commit 3a563189374737aa2ebf3f6467bf461bc3ec5443
|
| -Author: Pierre Tardy <pierre.tardy@intel.com>
|
| -Date: Mon Jul 15 18:48:35 2013 +0200
|
| -
|
| - logs: implement on the fly parsing of ansi code
|
| -
|
| - grunt and js tools have the habit of using ansi
|
| - code a lot, which are ugly inside python log window.
|
| - http://en.wikipedia.org/wiki/ANSI_escape_code
|
| -
|
| - This is a simple enough implementation of ansicode parser
|
| - which integrates well into buildbot's way of displaying logs
|
| -
|
| - Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
|
| -
|
| -diff --git a/master/buildbot/status/web/files/default.css b/master/buildbot/status/web/files/default.css
|
| -index d109be1..2d24c2b 100644
|
| ---- a/master/buildbot/status/web/files/default.css
|
| -+++ b/master/buildbot/status/web/files/default.css
|
| -@@ -564,6 +564,30 @@ span.stderr {
|
| - span.header {
|
| - color: blue;
|
| - }
|
| -+span.ansi30 {
|
| -+ color: black;
|
| -+}
|
| -+span.ansi31 {
|
| -+ color: red;
|
| -+}
|
| -+span.ansi32 {
|
| -+ color: green;
|
| -+}
|
| -+span.ansi33 {
|
| -+ color: orange;
|
| -+}
|
| -+span.ansi34 {
|
| -+ color: yellow;
|
| -+}
|
| -+span.ansi35 {
|
| -+ color: purple;
|
| -+}
|
| -+span.ansi36 {
|
| -+ color: blue;
|
| -+}
|
| -+span.ansi37 {
|
| -+ color: white;
|
| -+}
|
| -
|
| - /* revision & email */
|
| - .revision .full {
|
| -diff --git a/master/buildbot/status/web/logs.py b/master/buildbot/status/web/logs.py
|
| -index d7da811..b883dc2 100644
|
| ---- a/master/buildbot/status/web/logs.py
|
| -+++ b/master/buildbot/status/web/logs.py
|
| -@@ -24,6 +24,9 @@ from buildbot import interfaces
|
| - from buildbot.status import logfile
|
| - from buildbot.status.web.base import IHTMLLog, HtmlResource, path_to_root
|
| -
|
| -+import re
|
| -+
|
| -+
|
| - class ChunkConsumer:
|
| - implements(interfaces.IStatusLogConsumer)
|
| -
|
| -@@ -46,6 +49,7 @@ class ChunkConsumer:
|
| - def finish(self):
|
| - self.textlog.finished()
|
| -
|
| -+ANSI_RE = re.compile(r"^(\d*)(;(\d+))?([a-zA-Z])")
|
| -
|
| - # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname
|
| - class TextLog(Resource):
|
| -@@ -80,9 +84,26 @@ class TextLog(Resource):
|
| - # jinja only works with unicode, or pure ascii, so assume utf-8 in logs
|
| - if not isinstance(entry, unicode):
|
| - entry = unicode(entry, 'utf-8', 'replace')
|
| -- html_entries.append(dict(type = logfile.ChunkTypes[type],
|
| -- text = entry,
|
| -- is_header = is_header))
|
| -+ first_entry = True
|
| -+ _type = logfile.ChunkTypes[type]
|
| -+ for ansi_entry in entry.split("\033["):
|
| -+ code = ""
|
| -+ if not first_entry:
|
| -+ res = ANSI_RE.search(ansi_entry)
|
| -+ if res:
|
| -+ mode = res.group(4)
|
| -+ ansi_entry = ansi_entry[len(res.group(0)):]
|
| -+ if mode == 'm':
|
| -+ code = " ansi" + res.group(1)
|
| -+ else:
|
| -+ # illegal code, restore the CSI
|
| -+ ansi_entry = "\033[" + ansi_entry
|
| -+
|
| -+ html_entries.append(dict(type=_type + code,
|
| -+ text=ansi_entry,
|
| -+ is_header=is_header))
|
| -+ first_entry = False
|
| -+
|
| - elif not is_header:
|
| - text_data += entry
|
| -
|
| - ** https://github.com/buildbot/buildbot/commit/c451dd5c7cd377e774c4bf5c5d16d53986a9ccbf
|
| -commit c451dd5c7cd377e774c4bf5c5d16d53986a9ccbf
|
| -Author: Pierre Tardy <pierre.tardy@intel.com>
|
| -Date: Thu Aug 22 11:48:31 2013 +0200
|
| -
|
| - ansi codes: add support for multiple sgr classes
|
| -
|
| - Add some unit tests and factorize for potencial reuse
|
| -
|
| - nine plan is to reuse the regex and overall idea, but probably
|
| - rewrite this client side.
|
| -
|
| - Signed-off-by: Pierre Tardy <pierre.tardy@intel.com>
|
| -
|
| -diff --git a/master/buildbot/status/web/logs.py b/master/buildbot/status/web/logs.py
|
| -index b883dc2..37eaf29 100644
|
| ---- a/master/buildbot/status/web/logs.py
|
| -+++ b/master/buildbot/status/web/logs.py
|
| -@@ -23,9 +23,7 @@ from twisted.web.resource import Resource, NoResource
|
| - from buildbot import interfaces
|
| - from buildbot.status import logfile
|
| - from buildbot.status.web.base import IHTMLLog, HtmlResource, path_to_root
|
| --
|
| --import re
|
| --
|
| -+from buildbot.util.ansicodes import parse_ansi_sgr
|
| -
|
| - class ChunkConsumer:
|
| - implements(interfaces.IStatusLogConsumer)
|
| -@@ -49,8 +47,6 @@ class ChunkConsumer:
|
| - def finish(self):
|
| - self.textlog.finished()
|
| -
|
| --ANSI_RE = re.compile(r"^(\d*)(;(\d+))?([a-zA-Z])")
|
| --
|
| - # /builders/$builder/builds/$buildnum/steps/$stepname/logs/$logname
|
| - class TextLog(Resource):
|
| - # a new instance of this Resource is created for each client who views
|
| -@@ -77,7 +73,7 @@ class TextLog(Resource):
|
| - if type >= len(logfile.ChunkTypes) or type < 0:
|
| - # non-std channel, don't display
|
| - continue
|
| --
|
| -+
|
| - is_header = type == logfile.HEADER
|
| -
|
| - if not self.asText:
|
| -@@ -89,16 +85,9 @@ class TextLog(Resource):
|
| - for ansi_entry in entry.split("\033["):
|
| - code = ""
|
| - if not first_entry:
|
| -- res = ANSI_RE.search(ansi_entry)
|
| -- if res:
|
| -- mode = res.group(4)
|
| -- ansi_entry = ansi_entry[len(res.group(0)):]
|
| -- if mode == 'm':
|
| -- code = " ansi" + res.group(1)
|
| -- else:
|
| -- # illegal code, restore the CSI
|
| -- ansi_entry = "\033[" + ansi_entry
|
| --
|
| -+ ansi_entry, ansi_classes = parse_ansi_sgr(ansi_entry)
|
| -+ if ansi_classes:
|
| -+ code = "".join([" ansi" + i for i in ansi_classes])
|
| - html_entries.append(dict(type=_type + code,
|
| - text=ansi_entry,
|
| - is_header=is_header))
|
| -diff --git a/master/buildbot/test/unit/test_util_ansi_codes.py b/master/buildbot/test/unit/test_util_ansi_codes.py
|
| -new file mode 100644
|
| -index 0000000..f9ca02c
|
| ---- /dev/null
|
| -+++ b/master/buildbot/test/unit/test_util_ansi_codes.py
|
| -@@ -0,0 +1,39 @@
|
| -+# This file is part of Buildbot. Buildbot is free software: you can
|
| -+# redistribute it and/or modify it under the terms of the GNU General Public
|
| -+# License as published by the Free Software Foundation, version 2.
|
| -+#
|
| -+# This program is distributed in the hope that it will be useful, but WITHOUT
|
| -+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| -+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
| -+# details.
|
| -+#
|
| -+# You should have received a copy of the GNU General Public License along with
|
| -+# this program; if not, write to the Free Software Foundation, Inc., 51
|
| -+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| -+#
|
| -+# Copyright Buildbot Team Members
|
| -+
|
| -+from twisted.trial import unittest
|
| -+from buildbot.util.ansicodes import parse_ansi_sgr
|
| -+
|
| -+
|
| -+class TestAnsiCodes(unittest.TestCase):
|
| -+
|
| -+ def runTest(self, string, expected):
|
| -+ ret = parse_ansi_sgr(string)
|
| -+ self.assertEqual(ret, expected)
|
| -+
|
| -+ def test_ansi1m(self):
|
| -+ self.runTest("33mfoo", ("foo", ["33"]))
|
| -+
|
| -+ def test_ansi2m(self):
|
| -+ self.runTest("1;33mfoo", ("foo", ["1", "33"]))
|
| -+
|
| -+ def test_ansi5m(self):
|
| -+ self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"]))
|
| -+
|
| -+ def test_ansi_notm(self):
|
| -+ self.runTest("33xfoo", ("foo", []))
|
| -+
|
| -+ def test_ansi_invalid(self):
|
| -+ self.runTest("<>foo", ("\033[<>foo", []))
|
| -diff --git a/master/buildbot/util/ansicodes.py b/master/buildbot/util/ansicodes.py
|
| -new file mode 100644
|
| -index 0000000..bc58066
|
| ---- /dev/null
|
| -+++ b/master/buildbot/util/ansicodes.py
|
| -@@ -0,0 +1,36 @@
|
| -+# This file is part of Buildbot. Buildbot is free software: you can
|
| -+# redistribute it and/or modify it under the terms of the GNU General Public
|
| -+# License as published by the Free Software Foundation, version 2.
|
| -+#
|
| -+# This program is distributed in the hope that it will be useful, but WITHOUT
|
| -+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| -+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
| -+# details.
|
| -+#
|
| -+# You should have received a copy of the GNU General Public License along with
|
| -+# this program; if not, write to the Free Software Foundation, Inc., 51
|
| -+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| -+#
|
| -+# Copyright Buildbot Team Members
|
| -+
|
| -+import re
|
| -+
|
| -+ANSI_RE = re.compile(r"^((\d*)(;\d+)*)([a-zA-Z])")
|
| -+
|
| -+
|
| -+def parse_ansi_sgr(ansi_entry):
|
| -+ """simple utility to extract ansi sgr (Select Graphic Rendition) codes,
|
| -+ and ignore other codes.
|
| -+ Invalid codes are restored
|
| -+ """
|
| -+ classes = []
|
| -+ res = ANSI_RE.search(ansi_entry)
|
| -+ if res:
|
| -+ mode = res.group(4)
|
| -+ ansi_entry = ansi_entry[len(res.group(0)):]
|
| -+ if mode == 'm':
|
| -+ classes = res.group(1).split(";")
|
| -+ else:
|
| -+ # illegal code, restore the CSI
|
| -+ ansi_entry = "\033[" + ansi_entry
|
| -+ return ansi_entry, classes
|
| -
|
| - ** https://github.com/buildbot/buildbot/commit/94e11e9888ad3933cd5c3fc61d7e78ed3d0d79d1
|
| -commit 94e11e9888ad3933cd5c3fc61d7e78ed3d0d79d1
|
| -Author: Dustin J. Mitchell <dustin@mozilla.com>
|
| -Date: Thu Sep 26 08:34:36 2013 -0400
|
| -
|
| - render '\e[m' as a class-less SGR; fixes #2565
|
| -
|
| -diff --git a/master/buildbot/test/unit/test_util_ansi_codes.py b/master/buildbot/test/unit/test_util_ansi_codes.py
|
| -deleted file mode 100644
|
| -index bde441f..0000000
|
| ---- a/master/buildbot/test/unit/test_util_ansi_codes.py
|
| -+++ /dev/null
|
| -@@ -1,42 +0,0 @@
|
| --# This file is part of Buildbot. Buildbot is free software: you can
|
| --# redistribute it and/or modify it under the terms of the GNU General Public
|
| --# License as published by the Free Software Foundation, version 2.
|
| --#
|
| --# This program is distributed in the hope that it will be useful, but WITHOUT
|
| --# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| --# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
| --# details.
|
| --#
|
| --# You should have received a copy of the GNU General Public License along with
|
| --# this program; if not, write to the Free Software Foundation, Inc., 51
|
| --# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| --#
|
| --# Copyright Buildbot Team Members
|
| --
|
| --from twisted.trial import unittest
|
| --from buildbot.util.ansicodes import parse_ansi_sgr
|
| --
|
| --
|
| --class TestAnsiCodes(unittest.TestCase):
|
| --
|
| -- def runTest(self, string, expected):
|
| -- ret = parse_ansi_sgr(string)
|
| -- self.assertEqual(ret, expected)
|
| --
|
| -- def test_ansi1m(self):
|
| -- self.runTest("33mfoo", ("foo", ["33"]))
|
| --
|
| -- def test_ansi2m(self):
|
| -- self.runTest("1;33mfoo", ("foo", ["1", "33"]))
|
| --
|
| -- def test_ansi5m(self):
|
| -- self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"]))
|
| --
|
| -- def test_ansi_notm(self):
|
| -- self.runTest("33xfoo", ("foo", []))
|
| --
|
| -- def test_ansi_invalid(self):
|
| -- self.runTest("<>foo", ("\033[<>foo", []))
|
| --
|
| -- def test_ansi_invalid_start_by_semicolon(self):
|
| -- self.runTest(";3m", ("\033[;3m", []))
|
| -diff --git a/master/buildbot/test/unit/test_util_ansicodes.py b/master/buildbot/test/unit/test_util_ansicodes.py
|
| -new file mode 100644
|
| -index 0000000..e0d1926
|
| ---- /dev/null
|
| -+++ b/master/buildbot/test/unit/test_util_ansicodes.py
|
| -@@ -0,0 +1,45 @@
|
| -+# This file is part of Buildbot. Buildbot is free software: you can
|
| -+# redistribute it and/or modify it under the terms of the GNU General Public
|
| -+# License as published by the Free Software Foundation, version 2.
|
| -+#
|
| -+# This program is distributed in the hope that it will be useful, but WITHOUT
|
| -+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
| -+# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
| -+# details.
|
| -+#
|
| -+# You should have received a copy of the GNU General Public License along with
|
| -+# this program; if not, write to the Free Software Foundation, Inc., 51
|
| -+# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
| -+#
|
| -+# Copyright Buildbot Team Members
|
| -+
|
| -+from twisted.trial import unittest
|
| -+from buildbot.util.ansicodes import parse_ansi_sgr
|
| -+
|
| -+
|
| -+class TestAnsiCodes(unittest.TestCase):
|
| -+
|
| -+ def runTest(self, string, expected):
|
| -+ ret = parse_ansi_sgr(string)
|
| -+ self.assertEqual(ret, expected)
|
| -+
|
| -+ def test_ansi0m(self):
|
| -+ self.runTest("mfoo", ("foo", []))
|
| -+
|
| -+ def test_ansi1m(self):
|
| -+ self.runTest("33mfoo", ("foo", ["33"]))
|
| -+
|
| -+ def test_ansi2m(self):
|
| -+ self.runTest("1;33mfoo", ("foo", ["1", "33"]))
|
| -+
|
| -+ def test_ansi5m(self):
|
| -+ self.runTest("1;2;3;4;33mfoo", ("foo", ["1", "2", "3", "4", "33"]))
|
| -+
|
| -+ def test_ansi_notm(self):
|
| -+ self.runTest("33xfoo", ("foo", []))
|
| -+
|
| -+ def test_ansi_invalid(self):
|
| -+ self.runTest("<>foo", ("\033[<>foo", []))
|
| -+
|
| -+ def test_ansi_invalid_start_by_semicolon(self):
|
| -+ self.runTest(";3m", ("\033[;3m", []))
|
| -diff --git a/master/buildbot/util/ansicodes.py b/master/buildbot/util/ansicodes.py
|
| -index ebcf95f..01054ae 100644
|
| ---- a/master/buildbot/util/ansicodes.py
|
| -+++ b/master/buildbot/util/ansicodes.py
|
| -@@ -15,7 +15,7 @@
|
| -
|
| - import re
|
| -
|
| --ANSI_RE = re.compile(r"^((\d+)(;\d+)*)([a-zA-Z])")
|
| -+ANSI_RE = re.compile(r"^((\d+)(;\d+)*)?([a-zA-Z])")
|
| -
|
| -
|
| - def parse_ansi_sgr(ansi_entry):
|
| -@@ -29,7 +29,11 @@ def parse_ansi_sgr(ansi_entry):
|
| - mode = res.group(4)
|
| - ansi_entry = ansi_entry[len(res.group(0)):]
|
| - if mode == 'm':
|
| -- classes = res.group(1).split(";")
|
| -+ classes = res.group(1)
|
| -+ if classes:
|
| -+ classes = res.group(1).split(";")
|
| -+ else:
|
| -+ classes = []
|
| - else:
|
| - # illegal code, restore the CSI
|
| - ansi_entry = "\033[" + ansi_entry
|
| - ]
|
| -
|
| -
|
| -GitPoller used .split() instead of .split('\n') to split files. As a
|
| -result, it parsed a file with a space in the name as separate files.
|
| -
|
| ---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -@@ -200,7 +200,7 @@ class GitPoller(base.PollingChangeSource):
|
| - args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
|
| - d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
|
| - def process(git_output):
|
| -- fileList = git_output.split()
|
| -+ fileList = git_output.splitlines()
|
| - return fileList
|
| - d.addCallback(process)
|
| - return d
|
| -
|
| -
|
| -Cherry-pick change to allow serialization of sqlite access to de-flake unittests.
|
| -See https://github.com/buildbot/buildbot/commit/2c58b952ae93c45ee5bfe37f6cf70043da8b5d70.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
|
| -index 3d05cb6..cb711ce 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/enginestrategy.py
|
| -@@ -82,6 +82,11 @@ class BuildbotEngineStrategy(strategies.ThreadLocalEngineStrategy):
|
| - kwargs['pool_size'] = 1
|
| - max_conns = 1
|
| -
|
| -+ # allow serializing access to the db
|
| -+ if 'serialize_access' in u.query:
|
| -+ u.query.pop('serialize_access')
|
| -+ max_conns = 1
|
| -+
|
| - return u, kwargs, max_conns
|
| -
|
| - def set_up_sqlite_engine(self, u, engine):
|
| -
|
| -Enable serialized access from the above patch.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index cbebc0f..9a1b6d0 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -315,7 +315,8 @@ class BuildMaster(service.MultiService):
|
| - #change_source = config['change_source']
|
| -
|
| - # optional
|
| -- db_url = config.get("db_url", "sqlite:///state.sqlite")
|
| -+ db_url = config.get("db_url",
|
| -+ "sqlite:///state.sqlite?serialize_access=1")
|
| - db_poll_interval = config.get("db_poll_interval", None)
|
| - debugPassword = config.get('debugPassword')
|
| - manhole = config.get('manhole')
|
| -
|
| -Fix to file parsing. GitPoller produced blank lines
|
| -
|
| ---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -@@ -200,7 +200,8 @@ class GitPoller(base.PollingChangeSource):
|
| - args = ['log', rev, '--name-only', '--no-walk', r'--format=%n']
|
| - d = utils.getProcessOutput(self.gitbin, args, path=self.workdir, env=os.environ, errortoo=False )
|
| - def process(git_output):
|
| -- fileList = git_output.splitlines()
|
| -+ fileList = [f.strip() for f in git_output.splitlines()]
|
| -+ fileList = [f for f in fileList if f]
|
| - return fileList
|
| - d.addCallback(process)
|
| - return d
|
| -
|
| -GitPoller: made self.master.addChange call overridable, so it can be used in
|
| -TryJobGit.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -index 0b6d388..d5c18ba 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/changes/gitpoller.py
|
| -@@ -282,21 +282,31 @@ class GitPoller(base.PollingChangeSource):
|
| - revlink = self.revlinktmpl % urllib.quote_plus(rev)
|
| -
|
| - timestamp, name, files, comments = [ r[1] for r in results ]
|
| -- d = self.master.addChange(
|
| -+ d = self.add_change(
|
| - author=name,
|
| - revision=rev,
|
| - files=files,
|
| - comments=comments,
|
| - when_timestamp=epoch2datetime(timestamp),
|
| -- branch=self.branch,
|
| -- category=self.category,
|
| -- project=self.project,
|
| -- repository=self.repourl,
|
| - revlink=revlink)
|
| - wfd = defer.waitForDeferred(d)
|
| - yield wfd
|
| - results = wfd.getResult()
|
| -
|
| -+ def add_change(self, author, revision, files, comments, when_timestamp,
|
| -+ revlink):
|
| -+ return self.master.addChange(
|
| -+ author=author,
|
| -+ revision=revision,
|
| -+ files=files,
|
| -+ comments=comments,
|
| -+ when_timestamp=when_timestamp,
|
| -+ branch=self.branch,
|
| -+ category=self.category,
|
| -+ project=self.project,
|
| -+ repository=self.repourl,
|
| -+ revlink=revlink)
|
| -+
|
| - def _process_changes_failure(self, f):
|
| - log.msg('gitpoller: repo poll failed')
|
| - log.err(f)
|
| -
|
| -
|
| -Fix broken links to step names with '/' characters in them.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/build.py b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -index c634060..711d36a 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/build.py
|
| -@@ -115,7 +115,8 @@ class StatusResourceBuild(HtmlResource):
|
| -
|
| - cxt['steps'].append(step)
|
| -
|
| -- step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName()))
|
| -+ step['link'] = req.childLink("steps/%s" % urllib.quote(s.getName(),
|
| -+ safe=''))
|
| - step['text'] = " ".join(s.getText())
|
| - step['urls'] = map(lambda x:dict(url=x[1],logname=x[0]), s.getURLs().items())
|
| -
|
| -@@ -123,7 +124,7 @@ class StatusResourceBuild(HtmlResource):
|
| - for l in s.getLogs():
|
| - logname = l.getName()
|
| - step['logs'].append({ 'link': req.childLink("steps/%s/logs/%s" %
|
| -- (urllib.quote(s.getName()),
|
| -+ (urllib.quote(s.getName(), safe=''),
|
| - urllib.quote(logname))),
|
| - 'name': logname })
|
| -
|
| -
|
| -Backport BuildsetsConnectComponent.getRecentBuildsets from upstream
|
| -(1ee6d421be2ea814c11757263eb43152f8c3928e).
|
| -
|
| -Index: third_party/buildbot_8_4p1/buildbot/db/buildsets.py
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/buildsets.py b/third_party/buildbot_8_4p1/buildbot/db/buildsets.py
|
| -index e70a51242ca09ba7ec7034e2092f4053c3d0332c..c90af5bec8eb57e5075858623fcc5d59a62eadd7 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/buildsets.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/buildsets.py
|
| -@@ -190,6 +190,35 @@ class BuildsetsConnectorComponent(base.DBConnectorComponent):
|
| - return [ self._row2dict(row) for row in res.fetchall() ]
|
| - return self.db.pool.do(thd)
|
| -
|
| -+ def getRecentBuildsets(self, count, branch=None, repository=None,
|
| -+ complete=None):
|
| -+ def thd(conn):
|
| -+ bs_tbl = self.db.model.buildsets
|
| -+ ch_tbl = self.db.model.changes
|
| -+ j = sa.join(self.db.model.buildsets,
|
| -+ self.db.model.sourcestamps)
|
| -+ j = j.join(self.db.model.sourcestamp_changes)
|
| -+ j = j.join(ch_tbl)
|
| -+ q = sa.select(columns=[bs_tbl], from_obj=[j],
|
| -+ distinct=True)
|
| -+ q = q.order_by(sa.desc(bs_tbl.c.id))
|
| -+ q = q.limit(count)
|
| -+
|
| -+ if complete is not None:
|
| -+ if complete:
|
| -+ q = q.where(bs_tbl.c.complete != 0)
|
| -+ else:
|
| -+ q = q.where((bs_tbl.c.complete == 0) |
|
| -+ (bs_tbl.c.complete == None))
|
| -+ if branch:
|
| -+ q = q.where(ch_tbl.c.branch == branch)
|
| -+ if repository:
|
| -+ q = q.where(ch_tbl.c.repository == repository)
|
| -+ res = conn.execute(q)
|
| -+ return list(reversed([self._row2dict(row)
|
| -+ for row in res.fetchall()]))
|
| -+ return self.db.pool.do(thd)
|
| -+
|
| - def getBuildsetProperties(self, buildsetid):
|
| - """
|
| - Return the properties for a buildset, in the same format they were
|
| -Index: third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
|
| -index a6d4253cae43572992eac8d73b24cead97f63d9e..d28bcc61a72fd875af51c533b4ae189172225041 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_db_buildsets.py
|
| -@@ -441,3 +441,91 @@ class TestBuildsetsConnectorComponent(
|
| - d.addCallbacks(cb, eb)
|
| - return d
|
| -
|
| -+ def insert_test_getRecentBuildsets_data(self):
|
| -+ return self.insertTestData([
|
| -+ fakedb.Change(changeid=91, branch='branch_a', repository='repo_a'),
|
| -+ fakedb.SourceStamp(id=91, branch='branch_a', repository='repo_a'),
|
| -+ fakedb.SourceStampChange(sourcestampid=91, changeid=91),
|
| -+
|
| -+ fakedb.Buildset(id=91, sourcestampid=91, complete=0,
|
| -+ complete_at=298297875, results=-1, submitted_at=266761875,
|
| -+ external_idstring='extid', reason='rsn1'),
|
| -+ fakedb.Buildset(id=92, sourcestampid=91, complete=1,
|
| -+ complete_at=298297876, results=7, submitted_at=266761876,
|
| -+ external_idstring='extid', reason='rsn2'),
|
| -+
|
| -+ # buildset unrelated to the change
|
| -+ fakedb.Buildset(id=93, sourcestampid=1, complete=1,
|
| -+ complete_at=298297877, results=7, submitted_at=266761877,
|
| -+ external_idstring='extid', reason='rsn2'),
|
| -+ ])
|
| -+
|
| -+ def test_getRecentBuildsets_all(self):
|
| -+ d = self.insert_test_getRecentBuildsets_data()
|
| -+ d.addCallback(lambda _ :
|
| -+ self.db.buildsets.getRecentBuildsets(2, branch='branch_a',
|
| -+ repository='repo_a'))
|
| -+ def check(bsdictlist):
|
| -+ self.assertEqual(bsdictlist, [
|
| -+ dict(external_idstring='extid', reason='rsn1', sourcestampid=91,
|
| -+ submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 15,
|
| -+ tzinfo=UTC),
|
| -+ complete_at=datetime.datetime(1979, 6, 15, 12, 31, 15,
|
| -+ tzinfo=UTC),
|
| -+ complete=False, results=-1, bsid=91),
|
| -+ dict(external_idstring='extid', reason='rsn2', sourcestampid=91,
|
| -+ submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 16,
|
| -+ tzinfo=UTC),
|
| -+ complete_at=datetime.datetime(1979, 6, 15, 12, 31, 16,
|
| -+ tzinfo=UTC),
|
| -+ complete=True, results=7, bsid=92),
|
| -+ ])
|
| -+ d.addCallback(check)
|
| -+ return d
|
| -+
|
| -+ def test_getRecentBuildsets_one(self):
|
| -+ d = self.insert_test_getRecentBuildsets_data()
|
| -+ d.addCallback(lambda _ :
|
| -+ self.db.buildsets.getRecentBuildsets(1, branch='branch_a',
|
| -+ repository='repo_a'))
|
| -+ def check(bsdictlist):
|
| -+ self.assertEqual(bsdictlist, [
|
| -+ dict(external_idstring='extid', reason='rsn2', sourcestampid=91,
|
| -+ submitted_at=datetime.datetime(1978, 6, 15, 12, 31, 16,
|
| -+ tzinfo=UTC),
|
| -+ complete_at=datetime.datetime(1979, 6, 15, 12, 31, 16,
|
| -+ tzinfo=UTC),
|
| -+ complete=True, results=7, bsid=92),
|
| -+ ])
|
| -+ d.addCallback(check)
|
| -+ return d
|
| -+
|
| -+ def test_getRecentBuildsets_zero(self):
|
| -+ d = self.insert_test_getRecentBuildsets_data()
|
| -+ d.addCallback(lambda _ :
|
| -+ self.db.buildsets.getRecentBuildsets(0, branch='branch_a',
|
| -+ repository='repo_a'))
|
| -+ def check(bsdictlist):
|
| -+ self.assertEqual(bsdictlist, [])
|
| -+ d.addCallback(check)
|
| -+ return d
|
| -+
|
| -+ def test_getRecentBuildsets_noBranchMatch(self):
|
| -+ d = self.insert_test_getRecentBuildsets_data()
|
| -+ d.addCallback(lambda _ :
|
| -+ self.db.buildsets.getRecentBuildsets(2, branch='bad_branch',
|
| -+ repository='repo_a'))
|
| -+ def check(bsdictlist):
|
| -+ self.assertEqual(bsdictlist, [])
|
| -+ d.addCallback(check)
|
| -+ return d
|
| -+
|
| -+ def test_getRecentBuildsets_noRepoMatch(self):
|
| -+ d = self.insert_test_getRecentBuildsets_data()
|
| -+ d.addCallback(lambda _ :
|
| -+ self.db.buildsets.getRecentBuildsets(2, branch='branch_a',
|
| -+ repository='bad_repo'))
|
| -+ def check(bsdictlist):
|
| -+ self.assertEqual(bsdictlist, [])
|
| -+ d.addCallback(check)
|
| -+ return d
|
| -
|
| -Date: Mon Jun 9 15:17:46 2014 -0700
|
| -
|
| -Buildbot fix in triggerable.py
|
| -
|
| -The _waiters field is a dictionary, but it is treated as a list.
|
| -
|
| -R=agable@chromium.org
|
| -BUG=382693
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
|
| -index 0619977..19d6c4d 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/schedulers/triggerable.py
|
| -@@ -62,7 +62,7 @@ class Triggerable(base.BaseScheduler):
|
| - # and errback any outstanding deferreds
|
| - if self._waiters:
|
| - msg = 'Triggerable scheduler stopped before build was complete'
|
| -- for d in self._waiters:
|
| -+ for d in self._waiters.itervalues():
|
| - d.errback(failure.Failure(RuntimeError(msg)))
|
| - self._waiters = {}
|
| -
|
| -
|
| -Date: 2014-07-16 09:52:34 UTC
|
| -Added logging to the status_json.py
|
| -
|
| -BUG=393856
|
| -R=agable@chromium.org
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index e7cd932d10a0f874175a8b03bd0a9e29436e2278..1047dd55a791479f7be6143bc5992890eb6c4437 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -22,6 +22,7 @@ import os
|
| - import re
|
| -
|
| - from twisted.internet import defer
|
| -+from twisted.python import log as twlog
|
| - from twisted.web import html, resource, server
|
| -
|
| - from buildbot.status.web.base import HtmlResource
|
| -@@ -162,6 +163,10 @@ class JsonResource(resource.Resource):
|
| -
|
| - def render_GET(self, request):
|
| - """Renders a HTTP GET at the http request level."""
|
| -+ userAgent = request.requestHeaders.getRawHeaders(
|
| -+ 'user-agent', ['unknown'])[0]
|
| -+ twlog.msg('Received request for %s from %s, id: %s' %
|
| -+ (request.uri, userAgent, id(request)))
|
| - d = defer.maybeDeferred(lambda : self.content(request))
|
| - def handle(data):
|
| - if isinstance(data, unicode):
|
| -@@ -183,6 +188,7 @@ class JsonResource(resource.Resource):
|
| - return data
|
| - d.addCallback(handle)
|
| - def ok(data):
|
| -+ twlog.msg('Finished processing request with id: %s' % id(request))
|
| - request.write(data)
|
| - request.finish()
|
| - def fail(f):
|
| -
|
| -Date: Thu Jul 31 07:57:54 2014 -0400
|
| -
|
| - Console page: do revision filtering in the database instead of afterward
|
| -
|
| - Part of a set of changes split out from https://codereview.chromium.org/417513003/
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/changes.py b/third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -index a5b6482..c6516b6 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -@@ -194,7 +194,8 @@ class ChangesConnectorComponent(base.DBConnectorComponent):
|
| - d = self.db.pool.do(thd)
|
| - return d
|
| -
|
| -- def getRecentChanges(self, count):
|
| -+ def getRecentChanges(self, count, repository=None, author=None,
|
| -+ branch=None):
|
| - """
|
| - Get a list of the C{count} most recent changes, represented as
|
| - dictionaies; returns fewer if that many do not exist.
|
| -@@ -207,8 +208,15 @@ class ChangesConnectorComponent(base.DBConnectorComponent):
|
| - # get the changeids from the 'changes' table
|
| - changes_tbl = self.db.model.changes
|
| - q = sa.select([changes_tbl.c.changeid],
|
| -- order_by=[sa.desc(changes_tbl.c.changeid)],
|
| -- limit=count)
|
| -+ order_by=[sa.desc(changes_tbl.c.changeid)])
|
| -+ if repository:
|
| -+ q = q.where(changes_tbl.c.repository == repository)
|
| -+ if author:
|
| -+ q = q.where(changes_tbl.c.author == author)
|
| -+ if branch:
|
| -+ q = q.where(changes_tbl.c.branch == branch)
|
| -+ if count:
|
| -+ q = q.limit(count)
|
| - rp = conn.execute(q)
|
| - changeids = [ row.changeid for row in rp ]
|
| - rp.close()
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -index de922a4..fd4bd11 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -@@ -173,9 +173,10 @@ class ConsoleStatusResource(HtmlResource):
|
| - Every change is a line in the page, and it shows the result of the first
|
| - build with this change for each slave."""
|
| -
|
| -- def __init__(self, orderByTime=False):
|
| -+ def __init__(self, orderByTime=False, repository=None):
|
| - HtmlResource.__init__(self)
|
| -
|
| -+ self.repository = repository
|
| - self.status = None
|
| - self.cache = CacheStatus()
|
| -
|
| -@@ -244,11 +245,11 @@ class ConsoleStatusResource(HtmlResource):
|
| - return allChanges
|
| -
|
| - @defer.deferredGenerator
|
| -- def getAllChanges(self, request, status, debugInfo):
|
| -+ def getAllChanges(self, request, status, debugInfo, **kwargs):
|
| - master = request.site.buildbot_service.master
|
| - limit = min(100, max(1, int(request.args.get('limit', [25])[0])))
|
| - wfd = defer.waitForDeferred(
|
| -- master.db.changes.getRecentChanges(limit))
|
| -+ master.db.changes.getRecentChanges(limit, **kwargs))
|
| - yield wfd
|
| - chdicts = wfd.getResult()
|
| -
|
| -@@ -762,19 +763,14 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - # Get all changes we can find. This is a DB operation, so it must use
|
| - # a deferred.
|
| -- d = self.getAllChanges(request, status, debugInfo)
|
| -+ filter_branch = None if branch == ANYBRANCH else branch
|
| -+ filter_repo = repository if repository else self.repository
|
| -+ filter_author = devName[0] if devName else None
|
| -+ d = self.getAllChanges(request, status, debugInfo, author=filter_author,
|
| -+ branch=filter_branch, repository=filter_repo)
|
| - def got_changes(allChanges):
|
| - debugInfo["source_all"] = len(allChanges)
|
| --
|
| -- revFilter = {}
|
| -- if branch != ANYBRANCH:
|
| -- revFilter['branch'] = branch
|
| -- if devName:
|
| -- revFilter['who'] = devName
|
| -- if repository:
|
| -- revFilter['repository'] = repository
|
| -- revisions = list(self.filterRevisions(allChanges, max_revs=numRevs,
|
| -- filter=revFilter))
|
| -+ revisions = list(self.filterRevisions(allChanges, max_revs=numRevs))
|
| - debugInfo["revision_final"] = len(revisions)
|
| -
|
| - # Fetch all the builds for all builders until we get the next build
|
| -
|
| -Date: Wed Jul 30 12:11:07 2014 -0400
|
| -
|
| - Add optional builder filter function to console page
|
| -
|
| - This is needed for client.skia, where we want to hide trybots from the console.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -index fd4bd11..60383f4 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -@@ -173,10 +173,12 @@ class ConsoleStatusResource(HtmlResource):
|
| - Every change is a line in the page, and it shows the result of the first
|
| - build with this change for each slave."""
|
| -
|
| -- def __init__(self, orderByTime=False, repository=None):
|
| -+ def __init__(self, orderByTime=False, repository=None,
|
| -+ builder_filter_fn=lambda builderName: True):
|
| - HtmlResource.__init__(self)
|
| -
|
| - self.repository = repository
|
| -+ self.builder_filter_fn = builder_filter_fn
|
| - self.status = None
|
| - self.cache = CacheStatus()
|
| -
|
| -@@ -228,6 +230,8 @@ class ConsoleStatusResource(HtmlResource):
|
| - allChanges = list()
|
| - build_count = 0
|
| - for builderName in status.getBuilderNames()[:]:
|
| -+ if not self.builder_filter_fn(builderName):
|
| -+ continue
|
| - if build_count > max_builds:
|
| - break
|
| -
|
| -@@ -407,6 +411,8 @@ class ConsoleStatusResource(HtmlResource):
|
| - continue
|
| - if builders and builderName not in builders:
|
| - continue
|
| -+ if not self.builder_filter_fn(builderName):
|
| -+ continue
|
| -
|
| - # We want to display this builder.
|
| - category = builder.category or "default"
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -index 4a4f19d..92c9544 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -@@ -132,7 +132,8 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin):
|
| -
|
| - cxt['authz'] = self.getAuthz(req)
|
| - cxt['builder_url'] = path_to_builder(req, b)
|
| --
|
| -+ cxt['mastername'] = (
|
| -+ req.site.buildbot_service.master.properties['mastername'])
|
| - template = req.site.buildbot_service.templates.get_template("builder.html")
|
| - yield template.render(**cxt)
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -index 60383f4..2d48aca 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -@@ -77,14 +77,15 @@ class ANYBRANCH: pass # a flag value, used below
|
| -
|
| - class CachedStatusBox(object):
|
| - """Basic data class to remember the information for a box on the console."""
|
| -- def __init__(self, color, pageTitle, details, url, tag, builderName):
|
| -+ def __init__(self, color, pageTitle, details, url, tag, builderName,
|
| -+ buildNumber=None):
|
| - self.color = color
|
| - self.pageTitle = pageTitle
|
| - self.details = details
|
| - self.url = url
|
| - self.tag = tag
|
| - self.builderName = builderName
|
| --
|
| -+ self.buildNumber = buildNumber
|
| -
|
| - class CacheStatus(object):
|
| - """Basic cache of CachedStatusBox based on builder names and revisions.
|
| -@@ -105,9 +106,11 @@ class CacheStatus(object):
|
| - self.allBoxes[builder][revision].color)
|
| - return data
|
| -
|
| -- def insert(self, builderName, revision, color, pageTitle, details, url, tag):
|
| -+ def insert(self, builderName, revision, color, pageTitle, details, url,
|
| -+ tag, buildNumber=None):
|
| - """Insert a new build into the cache."""
|
| -- box = CachedStatusBox(color, pageTitle, details, url, tag, builderName)
|
| -+ box = CachedStatusBox(color, pageTitle, details, url, tag, builderName,
|
| -+ buildNumber)
|
| - if not self.allBoxes.get(builderName):
|
| - self.allBoxes[builderName] = {}
|
| -
|
| -@@ -285,7 +288,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - details = {}
|
| - if not build.getLogs():
|
| - return details
|
| --
|
| -+
|
| - for step in build.getSteps():
|
| - (result, reason) = step.getResults()
|
| - if result == builder.FAILURE:
|
| -@@ -294,7 +297,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - # Remove html tags from the error text.
|
| - stripHtml = re.compile(r'<.*?>')
|
| - strippedDetails = stripHtml.sub('', ' '.join(step.getText()))
|
| --
|
| -+
|
| - details['buildername'] = builderName
|
| - details['status'] = strippedDetails
|
| - details['reason'] = reason
|
| -@@ -304,7 +307,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - for log in step.getLogs():
|
| - logname = log.getName()
|
| - logurl = request.childLink(
|
| -- "../builders/%s/builds/%s/steps/%s/logs/%s" %
|
| -+ "../builders/%s/builds/%s/steps/%s/logs/%s" %
|
| - (urllib.quote(builderName, safe=''),
|
| - build.getNumber(),
|
| - urllib.quote(name, safe=''),
|
| -@@ -371,7 +374,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - def getChangeForBuild(self, build, revision):
|
| - if not build or not build.getChanges(): # Forced build
|
| - return DevBuild(revision, build, None)
|
| --
|
| -+
|
| - for change in build.getChanges():
|
| - if change.revision == revision:
|
| - return change
|
| -@@ -380,7 +383,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - changes = list(build.getChanges())
|
| - changes.sort(key=self.comparator.getSortingKey())
|
| - return changes[-1]
|
| --
|
| -+
|
| - def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds,
|
| - categories, builders, debugInfo):
|
| - """Returns a dictionary of builds we need to inspect to be able to
|
| -@@ -451,9 +454,9 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - categories = builderList.keys()
|
| - categories.sort()
|
| --
|
| -+
|
| - cs = []
|
| --
|
| -+
|
| - for category in categories:
|
| - c = {}
|
| -
|
| -@@ -526,14 +529,14 @@ class ConsoleStatusResource(HtmlResource):
|
| - # Sort the categories.
|
| - categories = builderList.keys()
|
| - categories.sort()
|
| --
|
| -+
|
| - builds = {}
|
| --
|
| -+
|
| - # Display the boxes by category group.
|
| - for category in categories:
|
| --
|
| -+
|
| - builds[category] = []
|
| --
|
| -+
|
| - # Display the boxes for each builder in this category.
|
| - for builder in builderList[category]:
|
| - introducedIn = None
|
| -@@ -549,6 +552,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - b["color"] = cached_value.color
|
| - b["tag"] = cached_value.tag
|
| - b["builderName"] = cached_value.builderName
|
| -+ b["buildNumber"] = cached_value.buildNumber
|
| -
|
| - builds[category].append(b)
|
| -
|
| -@@ -564,7 +568,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - break
|
| - else:
|
| - introducedIn = build
|
| --
|
| -+
|
| - # Get the results of the first build with the revision, and the
|
| - # first build that does not include the revision.
|
| - results = None
|
| -@@ -584,11 +588,13 @@ class ConsoleStatusResource(HtmlResource):
|
| - pageTitle = builder
|
| - tag = ""
|
| - current_details = {}
|
| -+ buildNumber = None
|
| - if introducedIn:
|
| - current_details = introducedIn.details or ""
|
| - url = "./buildstatus?builder=%s&number=%s" % (
|
| - urllib.quote(builder, safe=''),
|
| - introducedIn.number)
|
| -+ buildNumber = introducedIn.number
|
| - pageTitle += " "
|
| - pageTitle += urllib.quote(' '.join(introducedIn.text), ' \n\\/:')
|
| -
|
| -@@ -600,17 +606,17 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - if isRunning:
|
| - pageTitle += ' ETA: %ds' % (introducedIn.eta or 0)
|
| --
|
| -+
|
| - resultsClass = getResultsClass(results, previousResults, isRunning,
|
| - inProgressResults)
|
| -
|
| -- b = {}
|
| -+ b = {}
|
| - b["url"] = url
|
| - b["pageTitle"] = pageTitle
|
| - b["color"] = resultsClass
|
| - b["tag"] = tag
|
| - b["builderName"] = builder
|
| --
|
| -+ b["buildNumber"] = buildNumber
|
| - builds[category].append(b)
|
| -
|
| - # If the box is red, we add the explaination in the details
|
| -@@ -624,7 +630,8 @@ class ConsoleStatusResource(HtmlResource):
|
| - "notstarted"):
|
| - debugInfo["added_blocks"] += 1
|
| - self.cache.insert(builder, revision.revision, resultsClass,
|
| -- pageTitle, current_details, url, tag)
|
| -+ pageTitle, current_details, url, tag,
|
| -+ buildNumber)
|
| -
|
| - return (builds, details)
|
| -
|
| -@@ -685,10 +692,10 @@ class ConsoleStatusResource(HtmlResource):
|
| - # For each revision we show one line
|
| - for revision in revisions:
|
| - r = {}
|
| --
|
| -+
|
| - # Fill the dictionary with this new information
|
| - r['id'] = revision.revision
|
| -- r['link'] = revision.revlink
|
| -+ r['link'] = revision.revlink
|
| - r['who'] = revision.who
|
| - r['date'] = revision.date
|
| - r['comments'] = revision.comments
|
| -@@ -735,7 +742,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - if not reload_time:
|
| - reload_time = 60
|
| -
|
| -- # Append the tag to refresh the page.
|
| -+ # Append the tag to refresh the page.
|
| - if reload_time is not None and reload_time != 0:
|
| - cxt['refresh'] = reload_time
|
| -
|
| -@@ -810,6 +817,8 @@ class ConsoleStatusResource(HtmlResource):
|
| -
|
| - templates = request.site.buildbot_service.templates
|
| - template = templates.get_template("console.html")
|
| -+ cxt['mastername'] = (
|
| -+ request.site.buildbot_service.master.properties['mastername'])
|
| - data = template.render(cxt)
|
| -
|
| - # Clean up the cache.
|
| -@@ -825,9 +834,9 @@ class RevisionComparator(object):
|
| - VCS use a plain counter for revisions (like SVN)
|
| - while others use different concepts (see Git).
|
| - """
|
| --
|
| -+
|
| - # TODO (avivby): Should this be a zope interface?
|
| --
|
| -+
|
| - def isRevisionEarlier(self, first_change, second_change):
|
| - """Used for comparing 2 changes"""
|
| - raise NotImplementedError
|
| -@@ -838,7 +847,7 @@ class RevisionComparator(object):
|
| -
|
| - def getSortingKey(self):
|
| - raise NotImplementedError
|
| --
|
| -+
|
| - class TimeRevisionComparator(RevisionComparator):
|
| - def isRevisionEarlier(self, first, second):
|
| - return first.when < second.when
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
|
| -index 923fe0d..3262d1c 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/waterfall.py
|
| -@@ -172,7 +172,7 @@ class StepBox(components.Adapter):
|
| - text = []
|
| - text = text[:]
|
| - logs = self.original.getLogs()
|
| --
|
| -+
|
| - cxt = dict(text=text, logs=[], urls=[])
|
| -
|
| - for num in range(len(logs)):
|
| -@@ -188,9 +188,12 @@ class StepBox(components.Adapter):
|
| -
|
| - template = req.site.buildbot_service.templates.get_template("box_macros.html")
|
| - text = template.module.step_box(**cxt)
|
| --
|
| -+
|
| - class_ = "BuildStep " + build_get_class(self.original)
|
| -- return Box(text, class_=class_)
|
| -+ return Box(text, class_=class_,
|
| -+ buildNumber=self.original.build_number,
|
| -+ builder=self.original.builder.getName(),
|
| -+ stepName=self.original.getName())
|
| - components.registerAdapter(StepBox, buildstep.BuildStepStatus, IBox)
|
| -
|
| -
|
| -@@ -202,7 +205,7 @@ class EventBox(components.Adapter):
|
| - class_ = "Event"
|
| - return Box(text, class_=class_)
|
| - components.registerAdapter(EventBox, builder.Event, IBox)
|
| --
|
| -+
|
| -
|
| - class Spacer:
|
| - implements(interfaces.IStatusEvent)
|
| -@@ -301,7 +304,7 @@ class WaterfallHelp(HtmlResource):
|
| - times.insert(0, (current_reload_time, current_reload_time) )
|
| -
|
| - cxt['times'] = times
|
| -- cxt['current_reload_time'] = current_reload_time
|
| -+ cxt['current_reload_time'] = current_reload_time
|
| -
|
| - template = request.site.buildbot_service.templates.get_template("waterfallhelp.html")
|
| - return template.render(**cxt)
|
| -@@ -459,10 +462,10 @@ class WaterfallStatusResource(HtmlResource):
|
| - failuresOnly = request.args.get("failures_only", ["false"])[0]
|
| - if failuresOnly.lower() == "true":
|
| - builders = [b for b in builders if not self.isSuccess(b)]
|
| --
|
| -+
|
| - (changeNames, builderNames, timestamps, eventGrid, sourceEvents) = \
|
| - self.buildGrid(request, builders, changes)
|
| --
|
| -+
|
| - # start the table: top-header material
|
| - locale_enc = locale.getdefaultlocale()[1]
|
| - if locale_enc is not None:
|
| -@@ -471,19 +474,20 @@ class WaterfallStatusResource(HtmlResource):
|
| - locale_tz = unicode(time.tzname[time.localtime()[-1]])
|
| - ctx['tz'] = locale_tz
|
| - ctx['changes_url'] = request.childLink("../changes")
|
| --
|
| -+
|
| - bn = ctx['builders'] = []
|
| --
|
| -+
|
| - for name in builderNames:
|
| - builder = status.getBuilder(name)
|
| - top_box = ITopBox(builder).getBox(request)
|
| - current_box = ICurrentBox(builder).getBox(status, brcounts)
|
| - bn.append({'name': name,
|
| -- 'url': request.childLink("../builders/%s" % urllib.quote(name, safe='')),
|
| -- 'top': top_box.text,
|
| -+ 'category': builder.category,
|
| -+ 'url': request.childLink("../builders/%s" % urllib.quote(name, safe='')),
|
| -+ 'top': top_box.text,
|
| - 'top_class': top_box.class_,
|
| - 'status': current_box.text,
|
| -- 'status_class': current_box.class_,
|
| -+ 'status_class': current_box.class_,
|
| - })
|
| -
|
| - ctx.update(self.phase2(request, changeNames + builderNames, timestamps, eventGrid,
|
| -@@ -528,9 +532,11 @@ class WaterfallStatusResource(HtmlResource):
|
| - ctx['no_reload_page'] = with_args(request, remove_args=["reload"])
|
| -
|
| - template = request.site.buildbot_service.templates.get_template("waterfall.html")
|
| -+ ctx['mastername'] = (
|
| -+ request.site.buildbot_service.master.properties['mastername'])
|
| - data = template.render(**ctx)
|
| - return data
|
| --
|
| -+
|
| - def buildGrid(self, request, builders, changes):
|
| - debug = False
|
| - # TODO: see if we can use a cached copy
|
| -@@ -677,21 +683,21 @@ class WaterfallStatusResource(HtmlResource):
|
| -
|
| - if len(timestamps) > maxPageLen:
|
| - break
|
| --
|
| --
|
| -+
|
| -+
|
| - # now loop
|
| --
|
| -+
|
| - # loop is finished. now we have eventGrid[] and timestamps[]
|
| - if debugGather: log.msg("finished loop")
|
| - assert(len(timestamps) == len(eventGrid))
|
| - return (changeNames, builderNames, timestamps, eventGrid, sourceEvents)
|
| --
|
| -+
|
| - def phase2(self, request, sourceNames, timestamps, eventGrid,
|
| - sourceEvents):
|
| -
|
| - if not timestamps:
|
| - return dict(grid=[], gridlen=0)
|
| --
|
| -+
|
| - # first pass: figure out the height of the chunks, populate grid
|
| - grid = []
|
| - for i in range(1+len(sourceNames)):
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -index ca83fdb..af764f1 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -@@ -36,6 +36,7 @@ from buildbot.status.persistent_queue import DiskQueue, IndexedQueue, \
|
| - from buildbot.status.web.status_json import FilterOut
|
| - from twisted.internet import defer, reactor
|
| - from twisted.python import log
|
| -+from twisted.python.logfile import LogFile
|
| - from twisted.web import client
|
| -
|
| -
|
| -@@ -109,6 +110,9 @@ class StatusPush(StatusReceiverMultiService):
|
| - # Last shutdown was not clean, don't wait to send events.
|
| - self.queueNextServerPush()
|
| -
|
| -+ self.verboseLog = LogFile.fromFullPath(
|
| -+ 'status_push.log', rotateLength=10*1024*1024, maxRotatedFiles=14)
|
| -+
|
| - def startService(self):
|
| - """Starting up."""
|
| - StatusReceiverMultiService.startService(self)
|
| -@@ -390,6 +394,10 @@ class HttpStatusPush(StatusPush):
|
| - # This packet is just too large. Drop this packet.
|
| - log.msg("ERROR: packet %s was dropped, too large: %d > %d" %
|
| - (items[0]['id'], len(data), self.maxHttpRequestSize))
|
| -+ self.verboseLog.write(
|
| -+ "ERROR: packet %s was dropped, too large: %d > %d; %s" %
|
| -+ (items[0]['id'], len(data), self.maxHttpRequestSize,
|
| -+ json.dumps(items, indent=2, sort_keys=True)))
|
| - chunkSize = self.chunkSize
|
| - else:
|
| - # Try with half the packets.
|
| -@@ -405,6 +413,9 @@ class HttpStatusPush(StatusPush):
|
| - def Success(result):
|
| - """Queue up next push."""
|
| - log.msg('Sent %d events to %s' % (len(items), self.serverUrl))
|
| -+ self.verboseLog.write('Sent %d events to %s; %s' % (
|
| -+ len(items), self.serverUrl,
|
| -+ json.dumps(items, indent=2, sort_keys=True)))
|
| - self.lastPushWasSuccessful = True
|
| - return self.queueNextServerPush()
|
| -
|
| -@@ -413,6 +424,9 @@ class HttpStatusPush(StatusPush):
|
| - # Server is now down.
|
| - log.msg('Failed to push %d events to %s: %s' %
|
| - (len(items), self.serverUrl, str(result)))
|
| -+ self.verboseLog.write('Failed to push %d events to %s: %s; %s' %
|
| -+ (len(items), self.serverUrl, str(result),
|
| -+ json.dumps(items, indent=2, sort_keys=True)))
|
| - self.queue.insertBackChunk(items)
|
| - if self.stopped:
|
| - # Bad timing, was being called on shutdown and the server died
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/status_push.py b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -index af764f1..e8319e0 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/status_push.py
|
| -@@ -51,7 +51,7 @@ class StatusPush(StatusReceiverMultiService):
|
| - """
|
| -
|
| - def __init__(self, serverPushCb, queue=None, path=None, filter=True,
|
| -- bufferDelay=1, retryDelay=5, blackList=None):
|
| -+ bufferDelay=1, retryDelay=5, blackList=None, filterFunc=None):
|
| - """
|
| - @serverPushCb: callback to be used. It receives 'self' as parameter. It
|
| - should call self.queueNextServerPush() when it's done to queue the next
|
| -@@ -67,6 +67,7 @@ class StatusPush(StatusReceiverMultiService):
|
| - @retryDelay: amount of time between retries when no items were pushed on
|
| - last serverPushCb call.
|
| - @blackList: events that shouldn't be sent.
|
| -+ @filterFunc: optional function applied to items added to packet payload
|
| - """
|
| - StatusReceiverMultiService.__init__(self)
|
| -
|
| -@@ -90,6 +91,7 @@ class StatusPush(StatusReceiverMultiService):
|
| - return serverPushCb(self)
|
| - self.serverPushCb = hookPushCb
|
| - self.blackList = blackList
|
| -+ self.filterFunc = filterFunc
|
| -
|
| - # Other defaults.
|
| - # IDelayedCall object that represents the next queued push.
|
| -@@ -234,6 +236,8 @@ class StatusPush(StatusReceiverMultiService):
|
| - obj = obj.asDict()
|
| - if self.filter:
|
| - obj = FilterOut(obj)
|
| -+ if self.filterFunc:
|
| -+ obj = self.filterFunc(obj)
|
| - packet['payload'][obj_name] = obj
|
| - self.queue.pushItem(packet)
|
| - if self.task is None or not self.task.active():
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -index 9099a08..f5b4f43 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -@@ -272,6 +272,7 @@ class LoggedRemoteCommand(RemoteCommand):
|
| - def __init__(self, *args, **kwargs):
|
| - self.logs = {}
|
| - self.delayedLogs = {}
|
| -+ self.wasTimeout = False
|
| - self._closeWhenFinished = {}
|
| - RemoteCommand.__init__(self, *args, **kwargs)
|
| -
|
| -@@ -375,6 +376,8 @@ class LoggedRemoteCommand(RemoteCommand):
|
| - self.addHeader("program finished with exit code %d\n" % rc)
|
| - if update.has_key('elapsed'):
|
| - self._remoteElapsed = update['elapsed']
|
| -+ if update.has_key('timeout'):
|
| -+ self.wasTimeout = self.wasTimeout or update['timeout']
|
| -
|
| - for k in update:
|
| - if k not in ('stdout', 'stderr', 'header', 'rc'):
|
| -@@ -1241,6 +1244,8 @@ class LoggingBuildStep(BuildStep):
|
| -
|
| - if self.log_eval_func:
|
| - return self.log_eval_func(cmd, self.step_status)
|
| -+ if getattr(cmd, 'wasTimeout', False):
|
| -+ return EXCEPTION
|
| - if cmd.rc != 0:
|
| - return FAILURE
|
| - return SUCCESS
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
|
| -index e988807..3a582ef 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
|
| -@@ -60,5 +60,15 @@
|
| - Page built: <b>{{ time }}</b> ({{ tz }})
|
| - </div>
|
| - {% endblock -%}
|
| -+ <script>
|
| -+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
| -+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
| -+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
| -+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
| -+
|
| -+ ga('create', 'UA-55762617-2', 'auto');
|
| -+ ga('send', 'pageview');
|
| -+
|
| -+ </script>
|
| - </body>
|
| - </html>
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -index f5b4f43..160a4de 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/buildstep.py
|
| -@@ -1198,7 +1198,8 @@ class LoggingBuildStep(BuildStep):
|
| - self.step_status.setText(self.describe(True) +
|
| - ["exception", "slave", "lost"])
|
| - self.step_status.setText2(["exception", "slave", "lost"])
|
| -- return self.finished(RETRY)
|
| -+ # Retry if we're stopping the reactor (resarting the master)
|
| -+ return self.finished(RETRY if reactor._stopped else EXCEPTION)
|
| -
|
| - # to refine the status output, override one or more of the following
|
| - # methods. Change as little as possible: start with the first ones on
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
|
| -index 116bbd6..99708b4 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
|
| -@@ -4,6 +4,16 @@
|
| - <html>
|
| - <head><title>{{ pageTitle }}</title>
|
| - <link rel="stylesheet" href="{{ path_to_root }}default.css" type="text/css" />
|
| -+ <script>
|
| -+ (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
| -+ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
| -+ m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
| -+ })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
| -+
|
| -+ ga('create', 'UA-55762617-2', 'auto');
|
| -+ ga('send', 'pageview');
|
| -+
|
| -+ </script>
|
| - </head>
|
| - <body class='log'>
|
| - <a href="{{ texturl }}">(view as text)</a><br/>
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
|
| -index 3a582ef..a8a449e 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/layout.html
|
| -@@ -66,7 +66,7 @@
|
| - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
| - })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
| -
|
| -- ga('create', 'UA-55762617-2', 'auto');
|
| -+ ga('create', 'UA-55762617-2', {'siteSpeedSampleRate': 100});
|
| - ga('send', 'pageview');
|
| -
|
| - </script>
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
|
| -index 99708b4..bdd35c6 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/logs.html
|
| -@@ -10,7 +10,7 @@
|
| - m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
| - })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
| -
|
| -- ga('create', 'UA-55762617-2', 'auto');
|
| -+ ga('create', 'UA-55762617-2', {'siteSpeedSampleRate': 100});
|
| - ga('send', 'pageview');
|
| -
|
| - </script>
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -index 3ac7bbc..7703e57 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -@@ -602,11 +602,11 @@ class DuplicateSlaveArbitrator(object):
|
| - # slave to connect. If this does not kill it, then we disconnect
|
| - # the new slave.
|
| - self.ping_old_slave_done = False
|
| -+ self.ping_new_slave_done = False
|
| - self.old_slave_connected = True
|
| - self.ping_old_slave(new_tport.getPeer())
|
| -
|
| - # Print a message on the new slave, if possible.
|
| -- self.ping_new_slave_done = False
|
| - self.ping_new_slave()
|
| -
|
| - return self.new_slave_d
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -index 7703e57..7a83ed7 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -@@ -634,8 +634,14 @@ class DuplicateSlaveArbitrator(object):
|
| - self.ping_old_slave_timeout = reactor.callLater(self.PING_TIMEOUT, timeout)
|
| - self.ping_old_slave_timed_out = False
|
| -
|
| -- d = self.old_slave.slave.callRemote("print",
|
| -- "master got a duplicate connection from %s; keeping this one" % new_peer)
|
| -+ try:
|
| -+ d = self.old_slave.slave.callRemote(
|
| -+ "print",
|
| -+ "master got a duplicate connection from %s; keeping this one"
|
| -+ % new_peer)
|
| -+ except pb.DeadReferenceError():
|
| -+ timeout()
|
| -+ return
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index 9a1b6d0..3618b3c 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -217,16 +217,8 @@ class BuildMaster(service.MultiService):
|
| - bname, number_cancelled_builds))
|
| -
|
| - def noNewBuilds(self):
|
| -- log.msg("stopping schedulers")
|
| -- self.loadConfig_Schedulers([])
|
| -- log.msg("stopping sources")
|
| -- self.loadConfig_Sources([])
|
| -- d = self.cancelAllPendingBuilds()
|
| -- def doneStopping(res):
|
| -- log.msg("new builds stopped")
|
| -- return res
|
| -- d.addCallback(doneStopping)
|
| -- return d
|
| -+ log.msg("stopping build request driver")
|
| -+ return self.botmaster.brd.stopService()
|
| -
|
| - def loadTheConfigFile(self, configFile=None):
|
| - if not configFile:
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/master.py b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -index 3618b3c..d045b1e 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/master.py
|
| -@@ -995,7 +995,8 @@ class BuildMaster(service.MultiService):
|
| - """
|
| - d = self.db.buildsets.addBuildset(**kwargs)
|
| - def notify((bsid,brids)):
|
| -- log.msg("added buildset %d to database" % bsid)
|
| -+ log.msg("added buildset %d to database (build requests: %s)" %
|
| -+ (bsid, brids))
|
| - # note that buildset additions are only reported on this master
|
| - self._new_buildset_subs.deliver(bsid=bsid, **kwargs)
|
| - # only deliver messages immediately if we're not polling
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -index 7a83ed7..0c0bcef 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/botmaster.py
|
| -@@ -305,6 +305,7 @@ class BotMaster(service.MultiService):
|
| -
|
| - def startService(self):
|
| - def buildRequestAdded(notif):
|
| -+ log.msg("Processing new build request: %s" % notif)
|
| - self.maybeStartBuildsForBuilder(notif['buildername'])
|
| - self.buildrequest_sub = \
|
| - self.master.subscribeToBuildRequests(buildRequestAdded)
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/slave.py b/third_party/buildbot_8_4p1/buildbot/status/slave.py
|
| -index dbb8aab..21f7fb8 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/slave.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/slave.py
|
| -@@ -54,6 +54,8 @@ class SlaveStatus:
|
| - def getConnectCount(self):
|
| - then = time.time() - 3600
|
| - return len([ t for t in self.connect_times if t > then ])
|
| -+ def getConnectTimes(self):
|
| -+ return self.connect_times
|
| -
|
| - def setAdmin(self, admin):
|
| - self.admin = admin
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 9eb0858..1b36daf 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -27,7 +27,7 @@ from twisted.web import html, resource, server
|
| -
|
| - from buildbot.changes import changes
|
| - from buildbot.status.web.base import HtmlResource
|
| --from buildbot.util import json
|
| -+from buildbot.util import json, now
|
| -
|
| -
|
| - _IS_INT = re.compile('^[-+]?\d+$')
|
| -@@ -82,6 +82,8 @@ EXAMPLES = """\
|
| - - Builder information plus details information about its slaves. Neat eh?
|
| - - /json/slaves/<A_SLAVE>
|
| - - A specific slave.
|
| -+ - /json/buildstate
|
| -+ - The current build state.
|
| - - /json?select=slaves/<A_SLAVE>/&select=project&select=builders/<A_BUILDER>/builds/<A_BUILD>
|
| - - A selection of random unrelated stuff as an random example. :)
|
| - """
|
| -@@ -265,7 +267,7 @@ class JsonResource(resource.Resource):
|
| - if callback:
|
| - # Only accept things that look like identifiers for now
|
| - callback = callback[0]
|
| -- if re.match(r'^[a-zA-Z$][a-zA-Z$0-9.]*$', callback):
|
| -+ if re.match(r'^[_a-zA-Z$][_a-zA-Z$0-9.]*$', callback):
|
| - data = '%s(%s);' % (callback, data)
|
| - yield data
|
| -
|
| -@@ -349,6 +351,7 @@ class HelpResource(HtmlResource):
|
| - HtmlResource.__init__(self)
|
| - self.text = text
|
| - self.pageTitle = pageTitle
|
| -+ self.flags = getattr(parent_node, 'FLAGS', None) or FLAGS
|
| - self.parent_level = parent_node.level
|
| - self.parent_children = parent_node.children.keys()
|
| -
|
| -@@ -356,7 +359,7 @@ class HelpResource(HtmlResource):
|
| - cxt['level'] = self.parent_level
|
| - cxt['text'] = ToHtml(self.text)
|
| - cxt['children'] = [ n for n in self.parent_children if n != 'help' ]
|
| -- cxt['flags'] = ToHtml(FLAGS)
|
| -+ cxt['flags'] = ToHtml(self.flags)
|
| - cxt['examples'] = ToHtml(EXAMPLES).replace(
|
| - 'href="/json',
|
| - 'href="%sjson' % (self.level * '../'))
|
| -@@ -745,6 +748,204 @@ class MetricsJsonResource(JsonResource):
|
| - return None
|
| -
|
| -
|
| -+class BuildStateJsonResource(JsonResource):
|
| -+ help = ('Holistic build state JSON endpoint.\n\n'
|
| -+ 'This endpoint is a fast (unless otherwise noted) and '
|
| -+ 'comprehensive source for full BuildBot state queries. Any '
|
| -+ 'augmentations to this endpoint MUST keep this in mind.')
|
| -+
|
| -+ pageTitle = 'Build State JSON'
|
| -+
|
| -+ # Keyword for 'completed_builds' to indicate that all cached builds should
|
| -+ # be returned.
|
| -+ CACHED = 'cached'
|
| -+
|
| -+ EXTRA_FLAGS = """\
|
| -+ - builder
|
| -+ - A builder name to explicitly include in the results. This can be supplied
|
| -+ multiple times. If omitted, data for all builders will be returned.
|
| -+ - current_builds
|
| -+ - Controls whether the builder's current-running build data will be
|
| -+ returned. By default, no current build data will be returned; setting
|
| -+ current_builds=1 will enable this.
|
| -+ - completed_builds
|
| -+ - Controls whether the builder's completed build data will be returned. By
|
| -+ default, no completed build data will be retured. Setting
|
| -+ completed_builds=cached will return build data for all cached builds.
|
| -+ Setting it to a positive integer 'N' (e.g., completed_builds=3) will cause
|
| -+ data for the latest 'N' completed builds to be returned.
|
| -+ - pending_builds
|
| -+ - Controls whether the builder's pending build data will be
|
| -+ returned. By default, no pending build data will be returned; setting
|
| -+ pending_builds=1 will enable this.
|
| -+ - slaves
|
| -+ - Controls whether the builder's slave data will be returned. By default, no
|
| -+ slave build data will be returned; setting slaves=1 will enable this.
|
| -+"""
|
| -+ def __init__(self, status):
|
| -+ JsonResource.__init__(self, status)
|
| -+ self.FLAGS = FLAGS + self.EXTRA_FLAGS
|
| -+
|
| -+ self.putChild('project', ProjectJsonResource(status))
|
| -+
|
| -+ @classmethod
|
| -+ def _CountOrCachedRequestArg(cls, request, arg):
|
| -+ value = RequestArg(request, arg, 0)
|
| -+ if value == cls.CACHED:
|
| -+ return value
|
| -+ try:
|
| -+ value = int(value)
|
| -+ except ValueError:
|
| -+ return 0
|
| -+ return max(0, value)
|
| -+
|
| -+ @defer.deferredGenerator
|
| -+ def asDict(self, request):
|
| -+ builders = request.args.get('builder', ())
|
| -+ current_builds = RequestArgToBool(request, 'current_builds', False)
|
| -+ completed_builds = self._CountOrCachedRequestArg(request,
|
| -+ 'completed_builds')
|
| -+ pending_builds = RequestArgToBool(request, 'pending_builds', False)
|
| -+ slaves = RequestArgToBool(request, 'slaves', False)
|
| -+
|
| -+ builder_names = self.status.getBuilderNames()
|
| -+ if builders:
|
| -+ builder_names = [b for b in builder_names
|
| -+ if b in set(builders)]
|
| -+
|
| -+ # Collect child endpoint data.
|
| -+ wfd = defer.waitForDeferred(
|
| -+ defer.maybeDeferred(JsonResource.asDict, self, request))
|
| -+ yield wfd
|
| -+ response = wfd.getResult()
|
| -+
|
| -+ # Collect builder data.
|
| -+ wfd = defer.waitForDeferred(
|
| -+ defer.gatherResults(
|
| -+ [self._getBuilderData(self.status.getBuilder(builder_name),
|
| -+ current_builds, completed_builds,
|
| -+ pending_builds)
|
| -+ for builder_name in builder_names]))
|
| -+ yield wfd
|
| -+ response['builders'] = wfd.getResult()
|
| -+
|
| -+ # Add slave data.
|
| -+ if slaves:
|
| -+ response['slaves'] = self._getAllSlavesData()
|
| -+
|
| -+ # Add timestamp and return.
|
| -+ response['timestamp'] = now()
|
| -+ yield response
|
| -+
|
| -+ @defer.deferredGenerator
|
| -+ def _getBuilderData(self, builder, current_builds, completed_builds,
|
| -+ pending_builds):
|
| -+ # Load the builder dictionary. We use the synchronous path, since the
|
| -+ # asynchronous waits for pending builds to load. We handle that path
|
| -+ # explicitly via the 'pending_builds' option.
|
| -+ #
|
| -+ # This also causes the cache to be updated with recent builds, so we
|
| -+ # will call it first.
|
| -+ response = builder.asDict()
|
| -+ tasks = []
|
| -+
|
| -+ # Get current/completed builds.
|
| -+ if current_builds or completed_builds:
|
| -+ tasks.append(
|
| -+ defer.maybeDeferred(self._loadBuildData, builder,
|
| -+ current_builds, completed_builds))
|
| -+
|
| -+ # Get pending builds.
|
| -+ if pending_builds:
|
| -+ tasks.append(
|
| -+ self._loadPendingBuildData(builder))
|
| -+
|
| -+ # Collect a set of build data dictionaries to combine.
|
| -+ wfd = defer.waitForDeferred(
|
| -+ defer.gatherResults(tasks))
|
| -+ yield wfd
|
| -+ build_data_entries = wfd.getResult()
|
| -+
|
| -+ # Construct our build data from the various task entries.
|
| -+ build_state = response.setdefault('buildState', {})
|
| -+ for build_data_entry in build_data_entries:
|
| -+ build_state.update(build_data_entry)
|
| -+ yield response
|
| -+
|
| -+ def _loadBuildData(self, builder, current_builds, completed_builds):
|
| -+ build_state = {}
|
| -+ builds = set()
|
| -+ build_data_entries = []
|
| -+
|
| -+ current_build_numbers = set(b.getNumber()
|
| -+ for b in builder.currentBuilds)
|
| -+ if current_builds:
|
| -+ builds.update(current_build_numbers)
|
| -+ build_data_entries.append(('current', current_build_numbers))
|
| -+
|
| -+ if completed_builds:
|
| -+ if completed_builds == self.CACHED:
|
| -+ build_numbers = set(builder.buildCache.cache.keys())
|
| -+ build_numbers.difference_update(current_build_numbers)
|
| -+ else:
|
| -+ build_numbers = []
|
| -+ candidate = -1
|
| -+ while len(build_numbers) < completed_builds:
|
| -+ build_number = builder._resolveBuildNumber(candidate)
|
| -+ if not build_number:
|
| -+ break
|
| -+
|
| -+ candidate -= 1
|
| -+ if build_number in current_build_numbers:
|
| -+ continue
|
| -+ build_numbers.append(build_number)
|
| -+ builds.update(build_numbers)
|
| -+ build_data_entries.append(('completed', build_numbers))
|
| -+
|
| -+ # Load all builds referenced by 'builds'.
|
| -+ builds = builder.getBuilds(builds)
|
| -+ build_map = dict((build_dict['number'], build_dict)
|
| -+ for build_dict in [build.asDict()
|
| -+ for build in builds
|
| -+ if build])
|
| -+
|
| -+ # Map the collected builds to their repective keys. This dictionary
|
| -+ # takes the form: Build# => BuildDict.
|
| -+ for key, build_numbers in build_data_entries:
|
| -+ build_state[key] = dict((number, build_map.get(number, {}))
|
| -+ for number in build_numbers)
|
| -+ return build_state
|
| -+
|
| -+ def _loadPendingBuildData(self, builder):
|
| -+ d = builder.getPendingBuildRequestStatuses()
|
| -+
|
| -+ def cb_load_status(statuses):
|
| -+ statuses.sort(key=lambda s: s.getSubmitTime())
|
| -+ return defer.gatherResults([status.asDict_async()
|
| -+ for status in statuses])
|
| -+ d.addCallback(cb_load_status)
|
| -+
|
| -+ def cb_collect(status_dicts):
|
| -+ return {
|
| -+ 'pending': status_dicts,
|
| -+ }
|
| -+ d.addCallback(cb_collect)
|
| -+
|
| -+ return d
|
| -+
|
| -+ def _getAllSlavesData(self):
|
| -+ return dict((slavename, self._getSlaveData(slavename))
|
| -+ for slavename in self.status.getSlaveNames())
|
| -+
|
| -+ def _getSlaveData(self, slavename):
|
| -+ slave = self.status.getSlave(slavename)
|
| -+ return {
|
| -+ 'host': slave.getHost(),
|
| -+ 'connected': slave.isConnected(),
|
| -+ 'connect_times': slave.getConnectTimes(),
|
| -+ 'last_message_received': slave.lastMessageReceived(),
|
| -+ }
|
| -+
|
| -
|
| - class JsonStatusResource(JsonResource):
|
| - """Retrieves all json data."""
|
| -@@ -766,6 +967,7 @@ For help on any sub directory, use url /child/help
|
| - self.putChild('project', ProjectJsonResource(status))
|
| - self.putChild('slaves', SlavesJsonResource(status))
|
| - self.putChild('metrics', MetricsJsonResource(status))
|
| -+ self.putChild('buildstate', BuildStateJsonResource(status))
|
| - # This needs to be called before the first HelpResource().body call.
|
| - self.hackExamples()
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/build.py b/third_party/buildbot_8_4p1/buildbot/status/build.py
|
| -index ab3150f..723f6ad 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/build.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/build.py
|
| -@@ -433,29 +433,36 @@ class BuildStatus(styles.Versioned):
|
| - self.number))
|
| - log.err()
|
| -
|
| -- def asDict(self):
|
| -+ def asDict(self, blame=True, logs=True, properties=True, steps=True,
|
| -+ sourceStamp=True, eta=True):
|
| - result = {}
|
| - # Constant
|
| - result['builderName'] = self.builder.name
|
| - result['number'] = self.getNumber()
|
| -- result['sourceStamp'] = self.getSourceStamp().asDict()
|
| -+ if sourceStamp:
|
| -+ result['sourceStamp'] = self.getSourceStamp().asDict()
|
| - result['reason'] = self.getReason()
|
| -- result['blame'] = self.getResponsibleUsers()
|
| -+ if blame:
|
| -+ result['blame'] = self.getResponsibleUsers()
|
| -
|
| - # Transient
|
| -- result['properties'] = self.getProperties().asList()
|
| -+ if properties:
|
| -+ result['properties'] = self.getProperties().asList()
|
| - result['times'] = self.getTimes()
|
| - result['text'] = self.getText()
|
| - result['results'] = self.getResults()
|
| - result['slave'] = self.getSlavename()
|
| - # TODO(maruel): Add.
|
| - #result['test_results'] = self.getTestResults()
|
| -- result['logs'] = [[l.getName(),
|
| -- self.builder.status.getURLForThing(l)] for l in self.getLogs()]
|
| -- result['eta'] = self.getETA()
|
| -- result['steps'] = [bss.asDict() for bss in self.steps]
|
| -- if self.getCurrentStep():
|
| -- result['currentStep'] = self.getCurrentStep().asDict()
|
| -- else:
|
| -- result['currentStep'] = None
|
| -+ if logs:
|
| -+ result['logs'] = [[l.getName(),
|
| -+ self.builder.status.getURLForThing(l)] for l in self.getLogs()]
|
| -+ if eta:
|
| -+ result['eta'] = self.getETA()
|
| -+ if steps:
|
| -+ result['steps'] = [bss.asDict() for bss in self.steps]
|
| -+ if self.getCurrentStep():
|
| -+ result['currentStep'] = self.getCurrentStep().asDict()
|
| -+ else:
|
| -+ result['currentStep'] = None
|
| - return result
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 1b36daf..9fe46ef 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -760,6 +760,14 @@ class BuildStateJsonResource(JsonResource):
|
| - # be returned.
|
| - CACHED = 'cached'
|
| -
|
| -+ # Specific build fields that will be stripped unless requested. These map
|
| -+ # user-specified query parameters (lowercase) to
|
| -+ # buildbot.status.Build.asDict keyword arguments.
|
| -+ BUILD_FIELDS = ('blame', 'logs', 'sourceStamp', 'properties', 'steps',
|
| -+ 'eta',)
|
| -+ # Special build field to indicate all fields should be returned.
|
| -+ BUILD_FIELDS_ALL = 'all'
|
| -+
|
| - EXTRA_FLAGS = """\
|
| - - builder
|
| - - A builder name to explicitly include in the results. This can be supplied
|
| -@@ -771,20 +779,32 @@ class BuildStateJsonResource(JsonResource):
|
| - - completed_builds
|
| - - Controls whether the builder's completed build data will be returned. By
|
| - default, no completed build data will be retured. Setting
|
| -- completed_builds=cached will return build data for all cached builds.
|
| -- Setting it to a positive integer 'N' (e.g., completed_builds=3) will cause
|
| -- data for the latest 'N' completed builds to be returned.
|
| -+ completed_builds=%(completed_builds_cached)s will return build data for
|
| -+ all cached builds. Setting it to a positive integer 'N' (e.g.,
|
| -+ completed_builds=3) will cause data for the latest 'N' completed builds to
|
| -+ be returned.
|
| - - pending_builds
|
| - - Controls whether the builder's pending build data will be
|
| - returned. By default, no pending build data will be returned; setting
|
| - pending_builds=1 will enable this.
|
| -+ - build_field
|
| -+ - The specific build fields to include. Collecting and packaging more fields
|
| -+ will take more time. This can be supplied multiple times to request more
|
| -+ than one field. If '%(build_fields_all)s' is supplied, all build fields
|
| -+ will be included. Available individual fields are: %(build_fields)s.
|
| - - slaves
|
| - - Controls whether the builder's slave data will be returned. By default, no
|
| - slave build data will be returned; setting slaves=1 will enable this.
|
| - """
|
| - def __init__(self, status):
|
| - JsonResource.__init__(self, status)
|
| -- self.FLAGS = FLAGS + self.EXTRA_FLAGS
|
| -+ context = {
|
| -+ 'completed_builds_cached': self.CACHED,
|
| -+ 'build_fields_all': self.BUILD_FIELDS_ALL,
|
| -+ 'build_fields': ', '.join(sorted([f.lower()
|
| -+ for f in self.BUILD_FIELDS])),
|
| -+ }
|
| -+ self.FLAGS = FLAGS + self.EXTRA_FLAGS % context
|
| -
|
| - self.putChild('project', ProjectJsonResource(status))
|
| -
|
| -@@ -802,6 +822,7 @@ class BuildStateJsonResource(JsonResource):
|
| - @defer.deferredGenerator
|
| - def asDict(self, request):
|
| - builders = request.args.get('builder', ())
|
| -+ build_fields = request.args.get('build_field', ())
|
| - current_builds = RequestArgToBool(request, 'current_builds', False)
|
| - completed_builds = self._CountOrCachedRequestArg(request,
|
| - 'completed_builds')
|
| -@@ -824,7 +845,7 @@ class BuildStateJsonResource(JsonResource):
|
| - defer.gatherResults(
|
| - [self._getBuilderData(self.status.getBuilder(builder_name),
|
| - current_builds, completed_builds,
|
| -- pending_builds)
|
| -+ pending_builds, build_fields)
|
| - for builder_name in builder_names]))
|
| - yield wfd
|
| - response['builders'] = wfd.getResult()
|
| -@@ -839,7 +860,7 @@ class BuildStateJsonResource(JsonResource):
|
| -
|
| - @defer.deferredGenerator
|
| - def _getBuilderData(self, builder, current_builds, completed_builds,
|
| -- pending_builds):
|
| -+ pending_builds, build_fields):
|
| - # Load the builder dictionary. We use the synchronous path, since the
|
| - # asynchronous waits for pending builds to load. We handle that path
|
| - # explicitly via the 'pending_builds' option.
|
| -@@ -853,7 +874,8 @@ class BuildStateJsonResource(JsonResource):
|
| - if current_builds or completed_builds:
|
| - tasks.append(
|
| - defer.maybeDeferred(self._loadBuildData, builder,
|
| -- current_builds, completed_builds))
|
| -+ current_builds, completed_builds,
|
| -+ build_fields))
|
| -
|
| - # Get pending builds.
|
| - if pending_builds:
|
| -@@ -872,7 +894,8 @@ class BuildStateJsonResource(JsonResource):
|
| - build_state.update(build_data_entry)
|
| - yield response
|
| -
|
| -- def _loadBuildData(self, builder, current_builds, completed_builds):
|
| -+ def _loadBuildData(self, builder, current_builds, completed_builds,
|
| -+ build_fields):
|
| - build_state = {}
|
| - builds = set()
|
| - build_data_entries = []
|
| -@@ -902,10 +925,18 @@ class BuildStateJsonResource(JsonResource):
|
| - builds.update(build_numbers)
|
| - build_data_entries.append(('completed', build_numbers))
|
| -
|
| -+ # Determine which build fields to request.
|
| -+ build_fields = [f.lower() for f in build_fields]
|
| -+ build_field_kwargs = {}
|
| -+ if self.BUILD_FIELDS_ALL not in build_fields:
|
| -+ build_field_kwargs.update(dict(
|
| -+ (kwarg, kwarg.lower() in build_fields)
|
| -+ for kwarg in self.BUILD_FIELDS))
|
| -+
|
| - # Load all builds referenced by 'builds'.
|
| - builds = builder.getBuilds(builds)
|
| - build_map = dict((build_dict['number'], build_dict)
|
| -- for build_dict in [build.asDict()
|
| -+ for build_dict in [build.asDict(**build_field_kwargs)
|
| - for build in builds
|
| - if build])
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 9fe46ef..6c701ad 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -868,6 +868,7 @@ class BuildStateJsonResource(JsonResource):
|
| - # This also causes the cache to be updated with recent builds, so we
|
| - # will call it first.
|
| - response = builder.asDict()
|
| -+ response['builderName'] = builder.getName()
|
| - tasks = []
|
| -
|
| - # Get current/completed builds.
|
| -
|
| -Trim long comment fields to 1024 characters, but preserve important tags
|
| -
|
| -BUG=407345
|
| -R=agable@chromium.org,iannucci@chromium.org
|
| -
|
| -Index: third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/changes.py b/third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -index c6516b6fd26c86bedee27d35fbaeca667ae5697a..eff93e417e28db0e4a57c592e1cb9c7047433017 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/changes.py
|
| -@@ -127,10 +127,17 @@ class ChangesConnectorComponent(base.DBConnectorComponent):
|
| -
|
| - transaction = conn.begin()
|
| -
|
| -+ # Trim long comment fields to 1024 characters, but preserve header
|
| -+ # and footer with important tags such as Cr-Commit-Position.
|
| -+ trimmed_comments = comments
|
| -+ if len(trimmed_comments) > 1024:
|
| -+ header, footer = trimmed_comments[:506], trimmed_comments[-506:]
|
| -+ trimmed_comments = '%s\n...skip...\n%s' % (header, footer)
|
| -+
|
| - ins = self.db.model.changes.insert()
|
| - r = conn.execute(ins, dict(
|
| - author=author,
|
| -- comments=comments[:1024],
|
| -+ comments=trimmed_comments,
|
| - is_dir=is_dir,
|
| - branch=branch,
|
| - revision=revision,
|
| -Index: third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py b/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
|
| -index 875c5ea0d6f33190c53a36a1e4790d441ef12493..c51a06f5f0769ddafe6fc184d859b6080fcacde7 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/db/migrate/versions/001_initial.py
|
| -@@ -213,10 +213,17 @@ def import_changes(migrate_engine):
|
| - if not c.revision:
|
| - continue
|
| - try:
|
| -+ # Trim long comment fields to 1024 characters, but preserve header
|
| -+ # and footer with important tags such as Cr-Commit-Position.
|
| -+ trimmed_comments = c.comments
|
| -+ if len(trimmed_comments) > 1024:
|
| -+ header, footer = trimmed_comments[:506], trimmed_comments[-506:]
|
| -+ trimmed_comments = '%s\n...skip...\n%s' % (header, footer)
|
| -+
|
| - values = dict(
|
| - changeid=c.number,
|
| - author=c.who[:256],
|
| -- comments=c.comments[:1024],
|
| -+ comments=trimmed_comments,
|
| - is_dir=c.isdir,
|
| - branch=c.branch[:256],
|
| - revision=c.revision[:256],
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 6c701ad..36f8806 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -18,6 +18,7 @@
|
| -
|
| - import collections
|
| - import datetime
|
| -+import fnmatch
|
| - import os
|
| - import re
|
| -
|
| -@@ -772,6 +773,7 @@ class BuildStateJsonResource(JsonResource):
|
| - - builder
|
| - - A builder name to explicitly include in the results. This can be supplied
|
| - multiple times. If omitted, data for all builders will be returned.
|
| -+ Globbing via asterisk is permitted (e.g., "*_rel")
|
| - - current_builds
|
| - - Controls whether the builder's current-running build data will be
|
| - returned. By default, no current build data will be returned; setting
|
| -@@ -831,8 +833,10 @@ class BuildStateJsonResource(JsonResource):
|
| -
|
| - builder_names = self.status.getBuilderNames()
|
| - if builders:
|
| -+ builder_regex = re.compile('|'.join(fnmatch.translate(b)
|
| -+ for b in builders))
|
| - builder_names = [b for b in builder_names
|
| -- if b in set(builders)]
|
| -+ if builder_regex.match(b)]
|
| -
|
| - # Collect child endpoint data.
|
| - wfd = defer.waitForDeferred(
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 36f8806..36b1899 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -678,7 +678,7 @@ class SlaveJsonResource(JsonResource):
|
| - for build_status in builds:
|
| - if not build_status or not build_status.isFinished():
|
| - # If not finished, it will appear in runningBuilds.
|
| -- break
|
| -+ continue
|
| - slave = buildcache[build_status.getSlavename()]
|
| - slave.setdefault(builderName, []).append(
|
| - build_status.getNumber())
|
| -
|
| -
|
| -commit c956c1bf5b7c3726d80675d311e0a61d6d3f759c
|
| -Author: Ryan Kubiak <rkubiak@google.com>
|
| -Date: Thu Jan 22 17:19:49 2015 -0800
|
| -
|
| - Fix MailNotifier bug for buildsets
|
| -
|
| - Implementing a fix from buildbot 0.8.6p1.
|
| - http://trac.buildbot.net/ticket/2254
|
| - https://github.com/buildbot/buildbot/commit/4d3b0318025ca38c148a09e2fec5f2f97f61be57
|
| -
|
| - I wasn't entirely sure of the implications of removing self.db from
|
| - status/master.py. I found two files using status.master.db
|
| - (buildbot/status/builder.py, buildbot/status/web/status_json.py).
|
| - Neither of these are imports and they both seem to still work by
|
| - inherting status from their parent.
|
| -
|
| - I've run this patch on a testing instance and everything seems to still
|
| - work, although I have not tested the MailNotifier.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/mail.py b/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -index fd62b52..c03f609 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -@@ -332,12 +332,13 @@ class MailNotifier(base.StatusReceiverMultiService):
|
| - def setup(self):
|
| - self.master_status = self.parent.getStatus()
|
| - self.master_status.subscribe(self)
|
| -+ self.master = self.master_status.master
|
| -
|
| -
|
| - def startService(self):
|
| - if self.buildSetSummary:
|
| - self.buildSetSubscription = \
|
| -- self.parent.subscribeToBuildsetCompletions(self.buildsetFinished)
|
| -+ self.master.subscribeToBuildsetCompletions(self.buildsetFinished)
|
| -
|
| - base.StatusReceiverMultiService.startService(self)
|
| -
|
| -@@ -428,18 +429,18 @@ class MailNotifier(base.StatusReceiverMultiService):
|
| - for breq in breqs:
|
| - buildername = breq['buildername']
|
| - builders.append(self.master_status.getBuilder(buildername))
|
| -- d = self.parent.db.builds.getBuildsForRequest(breq['brid'])
|
| -+ d = self.master.db.builds.getBuildsForRequest(breq['brid'])
|
| - d.addCallback(builddicts.append)
|
| - dl.append(d)
|
| - d = defer.DeferredList(dl)
|
| - d.addCallback(self._gotBuilds, builddicts, buildset, builders)
|
| -
|
| - def _gotBuildSet(self, buildset, bsid):
|
| -- d = self.parent.db.buildrequests.getBuildRequests(bsid=bsid)
|
| -+ d = self.master.db.buildrequests.getBuildRequests(bsid=bsid)
|
| - d.addCallback(self._gotBuildRequests, buildset)
|
| -
|
| - def buildsetFinished(self, bsid, result):
|
| -- d = self.parent.db.buildsets.getBuildset(bsid=bsid)
|
| -+ d = self.master.db.buildsets.getBuildset(bsid=bsid)
|
| - d.addCallback(self._gotBuildSet, bsid)
|
| -
|
| - return d
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/master.py b/third_party/buildbot_8_4p1/buildbot/status/master.py
|
| -index 1df47a0..7864e2a 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/master.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/master.py
|
| -@@ -34,7 +34,6 @@ class Status:
|
| - def __init__(self, master):
|
| - self.master = master
|
| - self.botmaster = master.botmaster
|
| -- self.db = None
|
| - self.basedir = master.basedir
|
| - self.watchers = []
|
| - # compress logs bigger than 4k, a good default on linux
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py b/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py
|
| -index 3c8a70b..3ee4f49 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/test/unit/test_status_mail_MailNotifier.py
|
| -@@ -140,7 +140,7 @@ class TestMailNotifier(unittest.TestCase):
|
| - buildername='Builder'),
|
| - fakedb.Build(number=0, brid=11, results=SUCCESS)
|
| - ])
|
| -- mn.parent = self
|
| -+ mn.master = self # FIXME: Should be FakeMaster
|
| -
|
| - self.status = Mock()
|
| - mn.master_status = Mock()
|
| -
|
| -
|
| -commit eabc3bfef4de0456ef66a3eecac1689b4c18b4ea
|
| -Author: Ryan Kubiak <rkubiak@google.com>
|
| -Date: Mon Jan 26 12:10:52 2015 -0800
|
| -
|
| - Fix MailNotifier startup when buildsets are enabled
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/mail.py b/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -index c03f609..cd86e86 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/mail.py
|
| -@@ -327,22 +327,20 @@ class MailNotifier(base.StatusReceiverMultiService):
|
| - @type parent: L{buildbot.master.BuildMaster}
|
| - """
|
| - base.StatusReceiverMultiService.setServiceParent(self, parent)
|
| -- self.setup()
|
| -
|
| - def setup(self):
|
| - self.master_status = self.parent.getStatus()
|
| - self.master_status.subscribe(self)
|
| - self.master = self.master_status.master
|
| -
|
| --
|
| - def startService(self):
|
| -+ self.setup()
|
| - if self.buildSetSummary:
|
| - self.buildSetSubscription = \
|
| - self.master.subscribeToBuildsetCompletions(self.buildsetFinished)
|
| -
|
| - base.StatusReceiverMultiService.startService(self)
|
| -
|
| --
|
| - def stopService(self):
|
| - if self.buildSetSubscription is not None:
|
| - self.buildSetSubscription.unsubscribe()
|
| -
|
| -
|
| -commit 0832eba0b0d223ef028a84cd93956addc9173b0e
|
| -Author: Mike Stipicevic <stip@chromium.org>
|
| -Date: Wed Feb 11 17:23:44 2015 -0800
|
| -
|
| - Add server start time, current time, and uptime to json endpoint.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 36b1899..ff2758f 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -31,6 +31,22 @@ from buildbot.status.web.base import HtmlResource
|
| - from buildbot.util import json, now
|
| -
|
| -
|
| -+def get_timeblock():
|
| -+ def dt_to_ts(date):
|
| -+ return (date - datetime.datetime.utcfromtimestamp(0)).total_seconds()
|
| -+
|
| -+ utcnow = datetime.datetime.utcnow()
|
| -+
|
| -+ return {
|
| -+ 'local': datetime.datetime.now().isoformat(),
|
| -+ 'utc': utcnow.isoformat(),
|
| -+ 'utc_ts': dt_to_ts(utcnow),
|
| -+ }
|
| -+
|
| -+
|
| -+SERVER_STARTED = get_timeblock()
|
| -+
|
| -+
|
| - _IS_INT = re.compile('^[-+]?\d+$')
|
| -
|
| -
|
| -@@ -983,6 +999,23 @@ class BuildStateJsonResource(JsonResource):
|
| - }
|
| -
|
| -
|
| -+class MasterClockResource(JsonResource):
|
| -+ help = """Show current time, boot time and uptime of the master."""
|
| -+ pageTitle = 'MasterClock'
|
| -+
|
| -+ def asDict(self, _request):
|
| -+ # The only reason we include local time is because the buildbot UI
|
| -+ # displays it. Any computations on time should be done in UTC.
|
| -+ current_timeblock = get_timeblock()
|
| -+
|
| -+ return {
|
| -+ 'server_started': SERVER_STARTED,
|
| -+ 'current': current_timeblock,
|
| -+ 'server_uptime': (
|
| -+ current_timeblock['utc_ts'] - SERVER_STARTED['utc_ts'])
|
| -+ }
|
| -+
|
| -+
|
| - class JsonStatusResource(JsonResource):
|
| - """Retrieves all json data."""
|
| - help = """JSON status
|
| -@@ -1003,6 +1036,7 @@ For help on any sub directory, use url /child/help
|
| - self.putChild('project', ProjectJsonResource(status))
|
| - self.putChild('slaves', SlavesJsonResource(status))
|
| - self.putChild('metrics', MetricsJsonResource(status))
|
| -+ self.putChild('clock', MasterClockResource(status))
|
| - self.putChild('buildstate', BuildStateJsonResource(status))
|
| - # This needs to be called before the first HelpResource().body call.
|
| - self.hackExamples()
|
| -
|
| -commit 343c20bf0aff6a5240670a415e3ee41d7c488e8a
|
| -Author: Mike Stipicevic <stip@chromium.org>
|
| -Date: Fri Feb 20 14:44:41 2015 -0800
|
| -
|
| - Record submittedAt as a buildproperty.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/process/build.py b/third_party/buildbot_8_4p1/buildbot/process/build.py
|
| -index 04cf472..3376b5b 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/process/build.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/process/build.py
|
| -@@ -60,11 +60,15 @@ class Build:
|
| - finished = False
|
| - results = None
|
| - stopped = False
|
| -+ requestedAt = None
|
| -
|
| - def __init__(self, requests):
|
| - self.requests = requests
|
| - self.locks = []
|
| -
|
| -+ self.requestedAt = (
|
| -+ sorted(r.submittedAt for r in requests) or [None])[0]
|
| -+
|
| - # build a source stamp
|
| - self.source = requests[0].mergeWith(requests[1:])
|
| - self.reason = requests[0].mergeReasons(requests[1:])
|
| -@@ -183,6 +187,7 @@ class Build:
|
| - props.setProperty("revision", self.source.revision, "Build")
|
| - props.setProperty("repository", self.source.repository, "Build")
|
| - props.setProperty("project", self.source.project, "Build")
|
| -+ props.setProperty("requestedAt", self.requestedAt, "Build")
|
| - self.builder.setupProperties(props)
|
| -
|
| - def setupSlaveBuilder(self, slavebuilder):
|
| -
|
| -
|
| -commit d543bc773c6066d1a06ccd147020384429f67c57
|
| -Author: Mike Stipicevic <stip@chromium.org>
|
| -Date: Wed Feb 25 15:16:54 2015 -0800
|
| -
|
| - Report no-new-builds status.
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index ff2758f..8469c25 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -1016,6 +1016,16 @@ class MasterClockResource(JsonResource):
|
| - }
|
| -
|
| -
|
| -+class AcceptingBuildsResource(JsonResource):
|
| -+ help = """Show whether the master is scheduling new builds."""
|
| -+ pageTitle = 'AcceptingBuilds'
|
| -+
|
| -+ def asDict(self, _request):
|
| -+ return {
|
| -+ 'accepting_builds': bool(self.status.master.botmaster.brd.running),
|
| -+ }
|
| -+
|
| -+
|
| - class JsonStatusResource(JsonResource):
|
| - """Retrieves all json data."""
|
| - help = """JSON status
|
| -@@ -1037,6 +1047,7 @@ For help on any sub directory, use url /child/help
|
| - self.putChild('slaves', SlavesJsonResource(status))
|
| - self.putChild('metrics', MetricsJsonResource(status))
|
| - self.putChild('clock', MasterClockResource(status))
|
| -+ self.putChild('accepting_builds', AcceptingBuildsResource(status))
|
| - self.putChild('buildstate', BuildStateJsonResource(status))
|
| - # This needs to be called before the first HelpResource().body call.
|
| - self.hackExamples()
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 8469c25..531199b 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -876,6 +876,8 @@ class BuildStateJsonResource(JsonResource):
|
| -
|
| - # Add timestamp and return.
|
| - response['timestamp'] = now()
|
| -+ response['accepting_builds'] = bool(
|
| -+ self.status.master.botmaster.brd.running)
|
| - yield response
|
| -
|
| - @defer.deferredGenerator
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -index 92c9544..f9d5b82 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/builder.py
|
| -@@ -155,6 +155,10 @@ class StatusResourceBuilder(HtmlResource, BuildLineMixin):
|
| - if not self.getAuthz(req).actionAllowed('forceBuild', req, self.builder_status):
|
| - log.msg("..but not authorized")
|
| - return Redirect(path_to_authfail(req))
|
| -+ # ensure that they've filled out the username field at least.
|
| -+ if name == "<unknown>":
|
| -+ log.msg("..but didn't include a username to blame")
|
| -+ return Redirect(path_to_authfail(req))
|
| -
|
| - # keep weird stuff out of the branch revision, and property strings.
|
| - # TODO: centralize this somewhere.
|
| -
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html b/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html
|
| -index 0689e87..7ae3c9a 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/templates/buildslave.html
|
| -@@ -12,10 +12,9 @@
|
| - <ul>
|
| - {% for b in current %}
|
| - <li>{{ build_line(b, True) }}
|
| -- <form method="post" action="{{ b.buildurl }}/stop" class="command stopbuild" style="display:inline">
|
| -- <input type="submit" value="Stop Build" />
|
| -- <input type="hidden" name="url" value="{{ this_url }}" />
|
| -- </form>
|
| -+ {% if authz.advertiseAction('stopBuild') %}
|
| -+ {{ forms.stop_build(b.buildurl + '/stop', authz, on_all=False, short=True, label='Build') }}
|
| -+ {% endif %}
|
| - </li>
|
| - {% endfor %}
|
| - </ul>
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 531199b..8675da9 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -293,20 +293,17 @@ class JsonResource(resource.Resource):
|
| - """Generates the json dictionary.
|
| -
|
| - By default, renders every childs."""
|
| -- if self.children:
|
| -- data = {}
|
| -- for name in self.children:
|
| -- child = self.getChildWithDefault(name, request)
|
| -- if isinstance(child, JsonResource):
|
| -- wfd = defer.waitForDeferred(
|
| -- defer.maybeDeferred(lambda :
|
| -- child.asDict(request)))
|
| -- yield wfd
|
| -- data[name] = wfd.getResult()
|
| -- # else silently pass over non-json resources.
|
| -- yield data
|
| -- else:
|
| -- raise NotImplementedError()
|
| -+ data = {}
|
| -+ for name in self.children:
|
| -+ child = self.getChildWithDefault(name, request)
|
| -+ if isinstance(child, JsonResource):
|
| -+ wfd = defer.waitForDeferred(
|
| -+ defer.maybeDeferred(lambda :
|
| -+ child.asDict(request)))
|
| -+ yield wfd
|
| -+ data[name] = wfd.getResult()
|
| -+ # else silently pass over non-json resources.
|
| -+ yield data
|
| -
|
| -
|
| - def ToHtml(text):
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 8675da9..082b098 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -1025,6 +1025,42 @@ class AcceptingBuildsResource(JsonResource):
|
| - }
|
| -
|
| -
|
| -+class VarzResource(JsonResource):
|
| -+ help = 'Minimal set of metrics that are scraped frequently for monitoring.'
|
| -+ pageTitle = 'Varz'
|
| -+
|
| -+ @defer.deferredGenerator
|
| -+ def asDict(self, _request):
|
| -+ builders = {}
|
| -+ for builder_name in self.status.getBuilderNames():
|
| -+ builder = self.status.getBuilder(builder_name)
|
| -+ slaves = builder.getSlaves()
|
| -+ builders[builder_name] = {
|
| -+ 'connected_slaves': sum(1 for x in slaves if x.connected),
|
| -+ 'current_builds': len(builder.getCurrentBuilds()),
|
| -+ 'pending_builds': 0,
|
| -+ 'state': builder.currentBigState,
|
| -+ 'total_slaves': len(slaves),
|
| -+ }
|
| -+
|
| -+ # Get pending build requests directly from the db for all builders at
|
| -+ # once.
|
| -+ d = self.status.master.db.buildrequests.getBuildRequests(claimed=False)
|
| -+ def pending_builds_callback(brdicts):
|
| -+ for brdict in brdicts:
|
| -+ if brdict['buildername'] in builders:
|
| -+ builders[brdict['buildername']]['pending_builds'] += 1
|
| -+ d.addCallback(pending_builds_callback)
|
| -+ yield defer.waitForDeferred(d)
|
| -+
|
| -+ yield {
|
| -+ 'accepting_builds': bool(self.status.master.botmaster.brd.running),
|
| -+ 'builders': builders,
|
| -+ 'server_uptime': (
|
| -+ get_timeblock()['utc_ts'] - SERVER_STARTED['utc_ts']),
|
| -+ }
|
| -+
|
| -+
|
| - class JsonStatusResource(JsonResource):
|
| - """Retrieves all json data."""
|
| - help = """JSON status
|
| -@@ -1048,6 +1084,7 @@ For help on any sub directory, use url /child/help
|
| - self.putChild('clock', MasterClockResource(status))
|
| - self.putChild('accepting_builds', AcceptingBuildsResource(status))
|
| - self.putChild('buildstate', BuildStateJsonResource(status))
|
| -+ self.putChild('varz', VarzResource(status))
|
| - # This needs to be called before the first HelpResource().body call.
|
| - self.hackExamples()
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 082b098..84c0e09 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -27,6 +27,7 @@ from twisted.python import log as twlog
|
| - from twisted.web import html, resource, server
|
| -
|
| - from buildbot.changes import changes
|
| -+from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE
|
| - from buildbot.status.web.base import HtmlResource
|
| - from buildbot.util import json, now
|
| -
|
| -@@ -1029,15 +1030,49 @@ class VarzResource(JsonResource):
|
| - help = 'Minimal set of metrics that are scraped frequently for monitoring.'
|
| - pageTitle = 'Varz'
|
| -
|
| -+ RECENT_BUILDS_COUNT = 50
|
| -+
|
| - @defer.deferredGenerator
|
| - def asDict(self, _request):
|
| - builders = {}
|
| - for builder_name in self.status.getBuilderNames():
|
| - builder = self.status.getBuilder(builder_name)
|
| -+
|
| -+ build_range = range(-1, -self.RECENT_BUILDS_COUNT - 1, -1)
|
| -+ recent_builds = [b for b in builder.getBuilds(build_range)
|
| -+ if b is not None]
|
| -+ recent_pending_builds = [b for b in recent_builds
|
| -+ if not b.isFinished()]
|
| -+ recent_finished_builds = [b for b in recent_builds
|
| -+ if b.isFinished()]
|
| -+ recent_successful_builds = [
|
| -+ b for b in recent_finished_builds
|
| -+ if b.getResults() in (SUCCESS, WARNINGS)]
|
| -+ recent_infra_failed_builds = [
|
| -+ b for b in recent_finished_builds
|
| -+ if b.getResults() not in (SUCCESS, WARNINGS, FAILURE)]
|
| -+ recent_failed_builds = [
|
| -+ b for b in recent_finished_builds
|
| -+ if b.getResults() == FAILURE]
|
| -+
|
| -+ recent_successful_build_times = [
|
| -+ int(b.getTimes()[1] - b.getTimes()[0])
|
| -+ for b in recent_successful_builds]
|
| -+ recent_finished_build_times = [
|
| -+ int(b.getTimes()[1] - b.getTimes()[0])
|
| -+ for b in recent_finished_builds]
|
| -+
|
| - slaves = builder.getSlaves()
|
| - builders[builder_name] = {
|
| - 'connected_slaves': sum(1 for x in slaves if x.connected),
|
| - 'current_builds': len(builder.getCurrentBuilds()),
|
| -+ 'recent_builds': len(recent_builds),
|
| -+ 'recent_pending_builds': len(recent_pending_builds),
|
| -+ 'recent_successful_builds': len(recent_successful_builds),
|
| -+ 'recent_infra_failed_builds': len(recent_infra_failed_builds),
|
| -+ 'recent_failed_builds': len(recent_failed_builds),
|
| -+ 'recent_successful_build_times': recent_successful_build_times,
|
| -+ 'recent_finished_build_times': recent_finished_build_times,
|
| - 'pending_builds': 0,
|
| - 'state': builder.currentBigState,
|
| - 'total_slaves': len(slaves),
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -index 84c0e09..70cf371 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/status_json.py
|
| -@@ -1030,30 +1030,37 @@ class VarzResource(JsonResource):
|
| - help = 'Minimal set of metrics that are scraped frequently for monitoring.'
|
| - pageTitle = 'Varz'
|
| -
|
| -- RECENT_BUILDS_COUNT = 50
|
| -+ RECENT_BUILDS_COUNT_DEFAULT = 50
|
| -+ RECENT_BUILDS_COUNT_LIMIT = 200
|
| -
|
| - @defer.deferredGenerator
|
| -- def asDict(self, _request):
|
| -+ def asDict(self, request):
|
| -+ recent_builds_count = int(RequestArg(
|
| -+ request, 'recent_builds_count', self.RECENT_BUILDS_COUNT_DEFAULT))
|
| -+
|
| -+ # Enforce a hard limit to avoid DoS-ing buildbot with a heavy request.
|
| -+ recent_builds_count = min(
|
| -+ recent_builds_count, self.RECENT_BUILDS_COUNT_LIMIT)
|
| -+
|
| - builders = {}
|
| - for builder_name in self.status.getBuilderNames():
|
| - builder = self.status.getBuilder(builder_name)
|
| -
|
| -- build_range = range(-1, -self.RECENT_BUILDS_COUNT - 1, -1)
|
| -+ build_range = range(-1, -recent_builds_count - 1, -1)
|
| - recent_builds = [b for b in builder.getBuilds(build_range)
|
| - if b is not None]
|
| -- recent_pending_builds = [b for b in recent_builds
|
| -+ recent_running_builds = [b for b in recent_builds
|
| - if not b.isFinished()]
|
| - recent_finished_builds = [b for b in recent_builds
|
| - if b.isFinished()]
|
| - recent_successful_builds = [
|
| - b for b in recent_finished_builds
|
| - if b.getResults() in (SUCCESS, WARNINGS)]
|
| -- recent_infra_failed_builds = [
|
| -- b for b in recent_finished_builds
|
| -- if b.getResults() not in (SUCCESS, WARNINGS, FAILURE)]
|
| -- recent_failed_builds = [
|
| -- b for b in recent_finished_builds
|
| -- if b.getResults() == FAILURE]
|
| -+
|
| -+ recent_builds_by_status = collections.defaultdict(int)
|
| -+ recent_builds_by_status['running'] = len(recent_running_builds)
|
| -+ for b in recent_finished_builds:
|
| -+ recent_builds_by_status[b.getResults()] += 1
|
| -
|
| - recent_successful_build_times = [
|
| - int(b.getTimes()[1] - b.getTimes()[0])
|
| -@@ -1066,11 +1073,7 @@ class VarzResource(JsonResource):
|
| - builders[builder_name] = {
|
| - 'connected_slaves': sum(1 for x in slaves if x.connected),
|
| - 'current_builds': len(builder.getCurrentBuilds()),
|
| -- 'recent_builds': len(recent_builds),
|
| -- 'recent_pending_builds': len(recent_pending_builds),
|
| -- 'recent_successful_builds': len(recent_successful_builds),
|
| -- 'recent_infra_failed_builds': len(recent_infra_failed_builds),
|
| -- 'recent_failed_builds': len(recent_failed_builds),
|
| -+ 'recent_builds_by_status': recent_builds_by_status,
|
| - 'recent_successful_build_times': recent_successful_build_times,
|
| - 'recent_finished_build_times': recent_finished_build_times,
|
| - 'pending_builds': 0,
|
| -
|
| -commit 51ce3a66aadbbe847dd0501ad023f4598601f793
|
| -Author: Rico Wind <ricow@google.com>
|
| -Date: Fri Aug 21 13:08:28 2015 +0200
|
| -
|
| - Fix buildbot console view in order_by_time mode
|
| -
|
| - Applying a rebased version of http://trac.buildbot.net/ticket/2364
|
| -
|
| -diff --git a/third_party/buildbot_8_4p1/buildbot/status/web/console.py b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -index 2d48aca..36def52 100644
|
| ---- a/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -+++ b/third_party/buildbot_8_4p1/buildbot/status/web/console.py
|
| -@@ -158,7 +158,8 @@ class DevRevision:
|
| - class DevBuild:
|
| - """Helper class that contains all the information we need for a build."""
|
| -
|
| -- def __init__(self, revision, build, details, inProgressResults=None):
|
| -+ def __init__(self, revision, build, details, inProgressResults=None,
|
| -+ revisions=[]):
|
| - self.revision = revision
|
| - self.results = build.getResults()
|
| - self.number = build.getNumber()
|
| -@@ -169,6 +170,10 @@ class DevBuild:
|
| - self.when = build.getTimes()[0]
|
| - self.source = build.getSourceStamp()
|
| - self.inProgressResults = inProgressResults
|
| -+ for rev in revisions:
|
| -+ if rev.revision == revision:
|
| -+ self.when = rev.when
|
| -+ break
|
| -
|
| -
|
| - class ConsoleStatusResource(HtmlResource):
|
| -@@ -316,7 +321,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - return details
|
| -
|
| - def getBuildsForRevision(self, request, builder, builderName, lastRevision,
|
| -- numBuilds, debugInfo):
|
| -+ numBuilds, debugInfo, revisions):
|
| - """Return the list of all the builds for a given builder that we will
|
| - need to be able to display the console page. We start by the most recent
|
| - build, and we go down until we find a build that was built prior to the
|
| -@@ -357,7 +362,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - if got_rev and got_rev != -1:
|
| - details = self.getBuildDetails(request, builderName, build)
|
| - devBuild = DevBuild(got_rev, build, details,
|
| -- getInProgressResults(build))
|
| -+ getInProgressResults(build), revisions)
|
| - builds.append(devBuild)
|
| -
|
| - # Now break if we have enough builds.
|
| -@@ -385,7 +390,7 @@ class ConsoleStatusResource(HtmlResource):
|
| - return changes[-1]
|
| -
|
| - def getAllBuildsForRevision(self, status, request, lastRevision, numBuilds,
|
| -- categories, builders, debugInfo):
|
| -+ categories, builders, debugInfo, revisions):
|
| - """Returns a dictionary of builds we need to inspect to be able to
|
| - display the console page. The key is the builder name, and the value is
|
| - an array of build we care about. We also returns a dictionary of
|
| -@@ -436,7 +441,8 @@ class ConsoleStatusResource(HtmlResource):
|
| - builderName,
|
| - lastRevision,
|
| - numBuilds,
|
| -- debugInfo)
|
| -+ debugInfo,
|
| -+ revisions)
|
| -
|
| - return (builderList, allBuilds)
|
| -
|
| -@@ -800,7 +806,8 @@ class ConsoleStatusResource(HtmlResource):
|
| - numBuilds,
|
| - categories,
|
| - builders,
|
| -- debugInfo)
|
| -+ debugInfo,
|
| -+ revisions)
|
| -
|
| - debugInfo["added_blocks"] = 0
|
| - debugInfo["from_cache"] = 0
|
| + $ git diff ece89304e7e6e7f06016de19cdb623e3c7137d2e -- buildbot
|
|
|