3 # Imports to initialise twisted reactor and pygtk
8 from twisted.internet import gtk2reactor, defer
12 gnome.init('gbuildbotclient', '0.1')
17 import gobject, gtk.glade
18 from twisted.internet import reactor
19 from twisted.spread import pb
20 from twisted.cred import credentials
21 from twisted.python import log
22 from bbclient import BuildbotClient, CommandLineOptions
26 class App(pb.Referenceable):
28 # Model/View column identities
30 COL_BUILDER = 0 # PYOBJECT: RemoteBuilder objecr
31 COL_BUILDER_NAME = 1 # str: Name of builder
32 COL_BUILDER_STATE = 2 # str: State of builder
33 COL_BUILDER_BUILD_STATUS = 3 # str: State of current or last build
35 COL_BUILDER_BUILD_ETA_TEXT = 4 # str: Time remaining for build
36 COL_BUILDER_BUILD_ETA_PERCENT = 5 # int: Percent complete for build
37 COL_BUILDER_BUILD_ETA_TOTAL = 6 # int: Total estimated time for build
39 # Subscription modes. Each mode includes events of previous mode.
41 MODE_BUILDERS = 'builders' # builderAdded, builderRemoved
42 MODE_BUILDS = 'builds' # builderChangedState, buildStarted, buildFinished
43 MODE_STEPS = 'steps' # buildETAUpdate, stepStarted, stepFinished
44 MODE_LOGS = 'logs' # stepETAUpdate, logStarted, logFinished
45 MODE_FULL = 'full' # logChunk
49 Results = ["success", "warnings", "failure", "skipped", "exception"]
51 def __init__(self, host, port, username, password, updateInterval = 5):
53 # Initialise buildbot client
55 self.client = BuildbotClient()
57 # Initialise GUI stuff
59 self.xml = gtk.glade.XML('gbuildbotclient.glade')
61 self.win = self.xml.get_widget('toplevel')
62 self.win.connect('destroy', gtk.main_quit)
64 self.model = gtk.ListStore(
65 gobject.TYPE_PYOBJECT, str, str, str, str, int, int)
67 self.model.set_sort_column_id(
68 self.COL_BUILDER_STATE, gtk.SORT_ASCENDING)
70 view = gtk.TreeView(self.model)
72 def SortableTreeViewColumn(name, text):
73 c = gtk.TreeViewColumn(name, gtk.CellRendererText(), text = text)
76 c.set_sort_column_id(text)
81 SortableTreeViewColumn('Name', self.COL_BUILDER_NAME))
84 SortableTreeViewColumn('State', self.COL_BUILDER_STATE))
87 SortableTreeViewColumn(
88 'Build Status', self.COL_BUILDER_BUILD_STATUS))
90 col = gtk.TreeViewColumn('Build ETA',
91 gtk.CellRendererProgress(),
92 text = self.COL_BUILDER_BUILD_ETA_TEXT,
93 value = self.COL_BUILDER_BUILD_ETA_PERCENT)
95 col.set_clickable(True)
96 col.set_resizable(True)
97 col.set_sort_column_id(self.COL_BUILDER_BUILD_ETA_PERCENT)
100 view.append_column(col)
104 scrolledwindow = self.xml.get_widget('builders_scrolledwindow')
105 scrolledwindow.add_with_viewport(view)
106 scrolledwindow.show()
110 # Subscribe to build events to update main window
112 d = self.client.connect(host, port, username, password)
116 d = self.client.subscribe(self.MODE_STEPS, 5, self)
119 lambda *args: sys.stdout.write("error: %s\n" % args))
121 d.addCallback(subscribe)
123 # Callbacks for subscription mode >= MODE_BUILDERS
125 def remote_builderAdded(self, buildername, builder):
126 """Called by the PB server when a builder has been added to the
127 buildbot. The buildername parameter is the name of the build
128 as a string, and builder is a RemoteBuilder object."""
130 # Add builder to model
132 iter = self.model.append()
134 self.model.set_value(iter, self.COL_BUILDER, builder)
135 self.model.set_value(iter, self.COL_BUILDER_NAME, buildername)
136 self.model.set_value(iter, self.COL_BUILDER_BUILD_ETA_TEXT, 'n/a')
138 def remote_builderRemoved(self, buildername):
139 """Called by the PB server when a builder has been removed from the
142 # Remove builder from model
144 def delBuilder(model, path, iter, user_data):
145 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
146 self.model.remove(iter)
148 self.model.foreach(delBuilder, None)
150 # Callbacks for subscription mode >= MODE_BUILDS
152 def remote_buildStarted(self, buildername, build):
153 """Called by the PB server when a builder has started a build. The
154 buildername parameter is the name of the build as a string,
155 and build is a RemoteBuild object."""
157 # Update build status
159 def updateStatus(model, path, iter, user_data):
160 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
161 build.callRemote('getNumber').addCallback(
163 model.set_value(iter,
164 self.COL_BUILDER_BUILD_STATUS,
165 'Started build %d' % num))
167 self.model.foreach(updateStatus, None)
169 def remote_builderChangedState(self, buildername, statename, eta):
170 """Called by the PB server when a builder has changed state. The
171 buildername parameter is the name of the build, state is a
172 description of the state of the build, and eta is the
173 estimated time in seconds until the completion of the build."""
175 # Reflect state change in model
177 def updateState(model, path, iter, user_data):
179 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
183 model.set_value(iter, self.COL_BUILDER_STATE, statename)
187 if statename == 'building':
190 iter, self.COL_BUILDER_BUILD_STATUS, 'Unknown')
193 iter, self.COL_BUILDER_BUILD_ETA_TEXT, 'Unknown')
195 self.model.foreach(updateState, None)
197 def remote_buildFinished(self, buildername, build, result):
198 """Called by the PB server when a build has finished. Buildername is
199 the name of the build, build a RemoteBuild object, and result an
200 integer exit code being the result of the build."""
202 # Update build status
204 def updateStatus(model, path, iter, user_data):
206 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
208 # Update build status
210 dl = defer.DeferredList([build.callRemote('getNumber'),
211 build.callRemote('getResults')])
215 model.set_value(iter,
216 self.COL_BUILDER_BUILD_STATUS,
217 'Finished build %d: %s' %
218 (arg[0][1], self.Results[arg[1][1]])))
220 # Reset progress indicators
222 model.set_value(iter, self.COL_BUILDER_BUILD_ETA_TOTAL, 0)
223 model.set_value(iter, self.COL_BUILDER_BUILD_ETA_PERCENT, 0)
224 model.set_value(iter, self.COL_BUILDER_BUILD_ETA_TEXT, 'n/a')
226 self.model.foreach(updateStatus, None)
228 # Callbacks for subscription mode >= MODE_STEPS
230 def remote_stepStarted(self, buildername, build, stepname, step):
231 """Called by the PB server when a step in a build is started. The
232 buildername parameter is the name of the build, build is a
233 RemoteBuild object, stepname the name of the step, and step a
234 RemoteBuildStep object."""
236 # Update build status
238 def updateStatus(model, path, iter, user_data):
239 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
240 model.set_value(iter, self.COL_BUILDER_BUILD_STATUS,
241 'Started step "%s"' % stepname)
243 self.model.foreach(updateStatus, None)
245 def remote_stepFinished(self, buildername, build, stepname, step, results):
246 """Called by the PB server when a step in a build has finished. The
247 buildername parameter is the name of the build, build is a
248 remote build object, stepname the name of the step, step a
249 RemoteBuildStep object, and results is a tuple of result code
250 and a list of optional strings the step wants to append to the
251 overall build results."""
253 # Update build status
255 def updateStatus(model, path, iter, user_data):
256 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
257 self.model.set_value(iter, self.COL_BUILDER_BUILD_STATUS,
258 'Finished step "%s"' % stepname)
260 self.model.foreach(updateStatus, None)
262 def remote_buildETAUpdate(self, buildername, build, eta):
263 """Called by the PB server to update the ETA for the overall build.
264 The buidlername parameter is the name of he build, build is a
265 RemoteBuild object, and ETA the estimated time to completion
266 of the overall build."""
268 # Update build progress
270 def updateProgress(model, path, iter, user_data):
272 if model.get_value(iter, self.COL_BUILDER_NAME) == buildername:
275 sec = int(eta - min*60)
278 iter, self.COL_BUILDER_BUILD_ETA_TEXT,
279 '%d:%02d remaining' % (min, sec))
281 total = model.get_value(iter, self.COL_BUILDER_BUILD_ETA_TOTAL)
284 model.set_value(iter, self.COL_BUILDER_BUILD_ETA_TOTAL, eta)
288 iter, self.COL_BUILDER_BUILD_ETA_PERCENT,
289 100 * (total - eta) / total)
291 self.model.foreach(updateProgress, None)
293 def remote_stepETAUpdate(self, buildername, build, stepname, step,
295 """Called by the PB server to update the ETA for a step in a build.
296 The buidlername parameter is the name of the build, build a
297 RemoteBuild object, stepname the name of the step, step a
298 RemoteBuildStep object, eta the estimated time to completion o
299 the step, and expectations a tuple of (name, current, target)
300 where the value of current approaches target."""
304 def remote_logStarted(self, buildername, build, stepname, step,
306 """Called when the log for a build step is started."""
310 def remote_logFinished(self, buildername, build, stepname, step,
312 """Called when the log for a bild step is finished."""
318 if __name__ == '__main__':
320 # Command line parsing
322 host, port, user, password = CommandLineOptions()
326 log.startLogging(sys.stdout)
327 app = App(host, port, user, password)