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