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