summaryrefslogtreecommitdiffstats
path: root/bitbake/lib/bb/ui/crumbs/buildmanager.py
diff options
context:
space:
mode:
Diffstat (limited to 'bitbake/lib/bb/ui/crumbs/buildmanager.py')
-rw-r--r--bitbake/lib/bb/ui/crumbs/buildmanager.py457
1 files changed, 457 insertions, 0 deletions
diff --git a/bitbake/lib/bb/ui/crumbs/buildmanager.py b/bitbake/lib/bb/ui/crumbs/buildmanager.py
new file mode 100644
index 0000000000..f89e8eefd4
--- /dev/null
+++ b/bitbake/lib/bb/ui/crumbs/buildmanager.py
@@ -0,0 +1,457 @@
1#
2# BitBake Graphical GTK User Interface
3#
4# Copyright (C) 2008 Intel Corporation
5#
6# Authored by Rob Bradford <rob@linux.intel.com>
7#
8# This program is free software; you can redistribute it and/or modify
9# it under the terms of the GNU General Public License version 2 as
10# published by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program; if not, write to the Free Software Foundation, Inc.,
19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21import gtk
22import gobject
23import threading
24import os
25import datetime
26import time
27
28class BuildConfiguration:
29 """ Represents a potential *or* historic *or* concrete build. It
30 encompasses all the things that we need to tell bitbake to do to make it
31 build what we want it to build.
32
33 It also stored the metadata URL and the set of possible machines (and the
34 distros / images / uris for these. Apart from the metdata URL these are
35 not serialised to file (since they may be transient). In some ways this
36 functionality might be shifted to the loader class."""
37
38 def __init__ (self):
39 self.metadata_url = None
40
41 # Tuple of (distros, image, urls)
42 self.machine_options = {}
43
44 self.machine = None
45 self.distro = None
46 self.image = None
47 self.urls = []
48 self.extra_urls = []
49 self.extra_pkgs = []
50
51 def get_machines_model (self):
52 model = gtk.ListStore (gobject.TYPE_STRING)
53 for machine in self.machine_options.keys():
54 model.append ([machine])
55
56 return model
57
58 def get_distro_and_images_models (self, machine):
59 distro_model = gtk.ListStore (gobject.TYPE_STRING)
60
61 for distro in self.machine_options[machine][0]:
62 distro_model.append ([distro])
63
64 image_model = gtk.ListStore (gobject.TYPE_STRING)
65
66 for image in self.machine_options[machine][1]:
67 image_model.append ([image])
68
69 return (distro_model, image_model)
70
71 def get_repos (self):
72 self.urls = self.machine_options[self.machine][2]
73 return self.urls
74
75 # It might be a lot lot better if we stored these in like, bitbake conf
76 # file format.
77 @staticmethod
78 def load_from_file (filename):
79 f = open (filename, "r")
80
81 conf = BuildConfiguration()
82 for line in f.readlines():
83 data = line.split (";")[1]
84 if (line.startswith ("metadata-url;")):
85 conf.metadata_url = data.strip()
86 continue
87 if (line.startswith ("url;")):
88 conf.urls += [data.strip()]
89 continue
90 if (line.startswith ("extra-url;")):
91 conf.extra_urls += [data.strip()]
92 continue
93 if (line.startswith ("machine;")):
94 conf.machine = data.strip()
95 continue
96 if (line.startswith ("distribution;")):
97 conf.distro = data.strip()
98 continue
99 if (line.startswith ("image;")):
100 conf.image = data.strip()
101 continue
102
103 f.close ()
104 return conf
105
106 # Serialise to a file. This is part of the build process and we use this
107 # to be able to repeat a given build (using the same set of parameters)
108 # but also so that we can include the details of the image / machine /
109 # distro in the build manager tree view.
110 def write_to_file (self, filename):
111 f = open (filename, "w")
112
113 lines = []
114
115 if (self.metadata_url):
116 lines += ["metadata-url;%s\n" % (self.metadata_url)]
117
118 for url in self.urls:
119 lines += ["url;%s\n" % (url)]
120
121 for url in self.extra_urls:
122 lines += ["extra-url;%s\n" % (url)]
123
124 if (self.machine):
125 lines += ["machine;%s\n" % (self.machine)]
126
127 if (self.distro):
128 lines += ["distribution;%s\n" % (self.distro)]
129
130 if (self.image):
131 lines += ["image;%s\n" % (self.image)]
132
133 f.writelines (lines)
134 f.close ()
135
136class BuildResult(gobject.GObject):
137 """ Represents an historic build. Perhaps not successful. But it includes
138 things such as the files that are in the directory (the output from the
139 build) as well as a deserialised BuildConfiguration file that is stored in
140 ".conf" in the directory for the build.
141
142 This is GObject so that it can be included in the TreeStore."""
143
144 (STATE_COMPLETE, STATE_FAILED, STATE_ONGOING) = \
145 (0, 1, 2)
146
147 def __init__ (self, parent, identifier):
148 gobject.GObject.__init__ (self)
149 self.date = None
150
151 self.files = []
152 self.status = None
153 self.identifier = identifier
154 self.path = os.path.join (parent, identifier)
155
156 # Extract the date, since the directory name is of the
157 # format build-<year><month><day>-<ordinal> we can easily
158 # pull it out.
159 # TODO: Better to stat a file?
160 (_ , date, revision) = identifier.split ("-")
161 print date
162
163 year = int (date[0:4])
164 month = int (date[4:6])
165 day = int (date[6:8])
166
167 self.date = datetime.date (year, month, day)
168
169 self.conf = None
170
171 # By default builds are STATE_FAILED unless we find a "complete" file
172 # in which case they are STATE_COMPLETE
173 self.state = BuildResult.STATE_FAILED
174 for file in os.listdir (self.path):
175 if (file.startswith (".conf")):
176 conffile = os.path.join (self.path, file)
177 self.conf = BuildConfiguration.load_from_file (conffile)
178 elif (file.startswith ("complete")):
179 self.state = BuildResult.STATE_COMPLETE
180 else:
181 self.add_file (file)
182
183 def add_file (self, file):
184 # Just add the file for now. Don't care about the type.
185 self.files += [(file, None)]
186
187class BuildManagerModel (gtk.TreeStore):
188 """ Model for the BuildManagerTreeView. This derives from gtk.TreeStore
189 but it abstracts nicely what the columns mean and the setup of the columns
190 in the model. """
191
192 (COL_IDENT, COL_DESC, COL_MACHINE, COL_DISTRO, COL_BUILD_RESULT, COL_DATE, COL_STATE) = \
193 (0, 1, 2, 3, 4, 5, 6)
194
195 def __init__ (self):
196 gtk.TreeStore.__init__ (self,
197 gobject.TYPE_STRING,
198 gobject.TYPE_STRING,
199 gobject.TYPE_STRING,
200 gobject.TYPE_STRING,
201 gobject.TYPE_OBJECT,
202 gobject.TYPE_INT64,
203 gobject.TYPE_INT)
204
205class BuildManager (gobject.GObject):
206 """ This class manages the historic builds that have been found in the
207 "results" directory but is also used for starting a new build."""
208
209 __gsignals__ = {
210 'population-finished' : (gobject.SIGNAL_RUN_LAST,
211 gobject.TYPE_NONE,
212 ()),
213 'populate-error' : (gobject.SIGNAL_RUN_LAST,
214 gobject.TYPE_NONE,
215 ())
216 }
217
218 def update_build_result (self, result, iter):
219 # Convert the date into something we can sort by.
220 date = long (time.mktime (result.date.timetuple()))
221
222 # Add a top level entry for the build
223
224 self.model.set (iter,
225 BuildManagerModel.COL_IDENT, result.identifier,
226 BuildManagerModel.COL_DESC, result.conf.image,
227 BuildManagerModel.COL_MACHINE, result.conf.machine,
228 BuildManagerModel.COL_DISTRO, result.conf.distro,
229 BuildManagerModel.COL_BUILD_RESULT, result,
230 BuildManagerModel.COL_DATE, date,
231 BuildManagerModel.COL_STATE, result.state)
232
233 # And then we use the files in the directory as the children for the
234 # top level iter.
235 for file in result.files:
236 self.model.append (iter, (None, file[0], None, None, None, date, -1))
237
238 # This function is called as an idle by the BuildManagerPopulaterThread
239 def add_build_result (self, result):
240 gtk.gdk.threads_enter()
241 self.known_builds += [result]
242
243 self.update_build_result (result, self.model.append (None))
244
245 gtk.gdk.threads_leave()
246
247 def notify_build_finished (self):
248 # This is a bit of a hack. If we have a running build running then we
249 # will have a row in the model in STATE_ONGOING. Find it and make it
250 # as if it was a proper historic build (well, it is completed now....)
251
252 # We need to use the iters here rather than the Python iterator
253 # interface to the model since we need to pass it into
254 # update_build_result
255
256 iter = self.model.get_iter_first()
257
258 while (iter):
259 (ident, state) = self.model.get(iter,
260 BuildManagerModel.COL_IDENT,
261 BuildManagerModel.COL_STATE)
262
263 if state == BuildResult.STATE_ONGOING:
264 result = BuildResult (self.results_directory, ident)
265 self.update_build_result (result, iter)
266 iter = self.model.iter_next(iter)
267
268 def notify_build_succeeded (self):
269 # Write the "complete" file so that when we create the BuildResult
270 # object we put into the model
271
272 complete_file_path = os.path.join (self.cur_build_directory, "complete")
273 f = file (complete_file_path, "w")
274 f.close()
275 self.notify_build_finished()
276
277 def notify_build_failed (self):
278 # Without a "complete" file then this will mark the build as failed:
279 self.notify_build_finished()
280
281 # This function is called as an idle
282 def emit_population_finished_signal (self):
283 gtk.gdk.threads_enter()
284 self.emit ("population-finished")
285 gtk.gdk.threads_leave()
286
287 class BuildManagerPopulaterThread (threading.Thread):
288 def __init__ (self, manager, directory):
289 threading.Thread.__init__ (self)
290 self.manager = manager
291 self.directory = directory
292
293 def run (self):
294 # For each of the "build-<...>" directories ..
295
296 if os.path.exists (self.directory):
297 for directory in os.listdir (self.directory):
298
299 if not directory.startswith ("build-"):
300 continue
301
302 build_result = BuildResult (self.directory, directory)
303 self.manager.add_build_result (build_result)
304
305 gobject.idle_add (BuildManager.emit_population_finished_signal,
306 self.manager)
307
308 def __init__ (self, server, results_directory):
309 gobject.GObject.__init__ (self)
310
311 # The builds that we've found from walking the result directory
312 self.known_builds = []
313
314 # Save out the bitbake server, we need this for issuing commands to
315 # the cooker:
316 self.server = server
317
318 # The TreeStore that we use
319 self.model = BuildManagerModel ()
320
321 # The results directory is where we create (and look for) the
322 # build-<xyz>-<n> directories. We need to populate ourselves from
323 # directory
324 self.results_directory = results_directory
325 self.populate_from_directory (self.results_directory)
326
327 def populate_from_directory (self, directory):
328 thread = BuildManager.BuildManagerPopulaterThread (self, directory)
329 thread.start()
330
331 # Come up with the name for the next build ident by combining "build-"
332 # with the date formatted as yyyymmdd and then an ordinal. We do this by
333 # an optimistic algorithm incrementing the ordinal if we find that it
334 # already exists.
335 def get_next_build_ident (self):
336 today = datetime.date.today ()
337 datestr = str (today.year) + str (today.month) + str (today.day)
338
339 revision = 0
340 test_name = "build-%s-%d" % (datestr, revision)
341 test_path = os.path.join (self.results_directory, test_name)
342
343 while (os.path.exists (test_path)):
344 revision += 1
345 test_name = "build-%s-%d" % (datestr, revision)
346 test_path = os.path.join (self.results_directory, test_name)
347
348 return test_name
349
350 # Take a BuildConfiguration and then try and build it based on the
351 # parameters of that configuration. S
352 def do_build (self, conf):
353 server = self.server
354
355 # Work out the build directory. Note we actually create the
356 # directories here since we need to write the ".conf" file. Otherwise
357 # we could have relied on bitbake's builder thread to actually make
358 # the directories as it proceeds with the build.
359 ident = self.get_next_build_ident ()
360 build_directory = os.path.join (self.results_directory,
361 ident)
362 self.cur_build_directory = build_directory
363 os.makedirs (build_directory)
364
365 conffile = os.path.join (build_directory, ".conf")
366 conf.write_to_file (conffile)
367
368 # Add a row to the model representing this ongoing build. It's kinda a
369 # fake entry. If this build completes or fails then this gets updated
370 # with the real stuff like the historic builds
371 date = long (time.time())
372 self.model.append (None, (ident, conf.image, conf.machine, conf.distro,
373 None, date, BuildResult.STATE_ONGOING))
374 try:
375 server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1])
376 server.runCommand(["setVariable", "MACHINE", conf.machine])
377 server.runCommand(["setVariable", "DISTRO", conf.distro])
378 server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"])
379 server.runCommand(["setVariable", "BBFILES", \
380 """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""])
381 server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"])
382 server.runCommand(["setVariable", "IPK_FEED_URIS", \
383 " ".join(conf.get_repos())])
384 server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE",
385 build_directory])
386 server.runCommand(["buildTargets", [conf.image], "rootfs"])
387
388 except Exception, e:
389 print e
390
391class BuildManagerTreeView (gtk.TreeView):
392 """ The tree view for the build manager. This shows the historic builds
393 and so forth. """
394
395 # We use this function to control what goes in the cell since we store
396 # the date in the model as seconds since the epoch (for sorting) and so we
397 # need to make it human readable.
398 def date_format_custom_cell_data_func (self, col, cell, model, iter):
399 date = model.get (iter, BuildManagerModel.COL_DATE)[0]
400 datestr = time.strftime("%A %d %B %Y", time.localtime(date))
401 cell.set_property ("text", datestr)
402
403 # This format function controls what goes in the cell. We use this to map
404 # the integer state to a string and also to colourise the text
405 def state_format_custom_cell_data_fun (self, col, cell, model, iter):
406 state = model.get (iter, BuildManagerModel.COL_STATE)[0]
407
408 if (state == BuildResult.STATE_ONGOING):
409 cell.set_property ("text", "Active")
410 cell.set_property ("foreground", "#000000")
411 elif (state == BuildResult.STATE_FAILED):
412 cell.set_property ("text", "Failed")
413 cell.set_property ("foreground", "#ff0000")
414 elif (state == BuildResult.STATE_COMPLETE):
415 cell.set_property ("text", "Complete")
416 cell.set_property ("foreground", "#00ff00")
417 else:
418 cell.set_property ("text", "")
419
420 def __init__ (self):
421 gtk.TreeView.__init__(self)
422
423 # Misc descriptiony thing
424 renderer = gtk.CellRendererText ()
425 col = gtk.TreeViewColumn (None, renderer,
426 text=BuildManagerModel.COL_DESC)
427 self.append_column (col)
428
429 # Machine
430 renderer = gtk.CellRendererText ()
431 col = gtk.TreeViewColumn ("Machine", renderer,
432 text=BuildManagerModel.COL_MACHINE)
433 self.append_column (col)
434
435 # distro
436 renderer = gtk.CellRendererText ()
437 col = gtk.TreeViewColumn ("Distribution", renderer,
438 text=BuildManagerModel.COL_DISTRO)
439 self.append_column (col)
440
441 # date (using a custom function for formatting the cell contents it
442 # takes epoch -> human readable string)
443 renderer = gtk.CellRendererText ()
444 col = gtk.TreeViewColumn ("Date", renderer,
445 text=BuildManagerModel.COL_DATE)
446 self.append_column (col)
447 col.set_cell_data_func (renderer,
448 self.date_format_custom_cell_data_func)
449
450 # For status.
451 renderer = gtk.CellRendererText ()
452 col = gtk.TreeViewColumn ("Status", renderer,
453 text = BuildManagerModel.COL_STATE)
454 self.append_column (col)
455 col.set_cell_data_func (renderer,
456 self.state_format_custom_cell_data_fun)
457