result.identifier, BuildManagerModel.COL_DESC, result.conf.image, BuildManagerModel.COL_MACHINE, result.conf.machine, BuildManagerModel.COL_DISTRO, result.conf.distro, BuildManagerModel.COL_BUILD_RESULT, result, BuildManagerModel.COL_DATE, date, BuildManagerModel.COL_STATE, result.state) # And then we use the files in the directory as the children for the # top level iter. for file in result.files: self.model.append (iter, (None, file[0], None, None, None, date, -1)) # This function is called as an idle by the BuildManagerPopulaterThread def add_build_result (self, result): gtk.gdk.threads_enter() self.known_builds += [result] self.update_build_result (result, self.model.append (None)) gtk.gdk.threads_leave() def notify_build_finished (self): # This is a bit of a hack. If we have a running build running then we # will have a row in the model in STATE_ONGOING. Find it and make it # as if it was a proper historic build (well, it is completed now....) # We need to use the iters here rather than the Python iterator # interface to the model since we need to pass it into # update_build_result iter = self.model.get_iter_first() while (iter): (ident, state) = self.model.get(iter, BuildManagerModel.COL_IDENT, BuildManagerModel.COL_STATE) if state == BuildResult.STATE_ONGOING: result = BuildResult (self.results_directory, ident) self.update_build_result (result, iter) iter = self.model.iter_next(iter) def notify_build_succeeded (self): # Write the "complete" file so that when we create the BuildResult # object we put into the model complete_file_path = os.path.join (self.cur_build_directory, "complete") f = file (complete_file_path, "w") f.close() self.notify_build_finished() def notify_build_failed (self): # Without a "complete" file then this will mark the build as failed: self.notify_build_finished() # This function is called as an idle def emit_population_finished_signal (self): gtk.gdk.threads_enter() self.emit ("population-finished") gtk.gdk.threads_leave() class BuildManagerPopulaterThread (threading.Thread): def __init__ (self, manager, directory): threading.Thread.__init__ (self) self.manager = manager self.directory = directory def run (self): # For each of the "build-<...>" directories .. if os.path.exists (self.directory): for directory in os.listdir (self.directory): if not directory.startswith ("build-"): continue build_result = BuildResult (self.directory, directory) self.manager.add_build_result (build_result) gobject.idle_add (BuildManager.emit_population_finished_signal, self.manager) def __init__ (self, server, results_directory): gobject.GObject.__init__ (self) # The builds that we've found from walking the result directory self.known_builds = [] # Save out the bitbake server, we need this for issuing commands to # the cooker: self.server = server # The TreeStore that we use self.model = BuildManagerModel () # The results directory is where we create (and look for) the # build-<xyz>-<n> directories. We need to populate ourselves from # directory self.results_directory = results_directory self.populate_from_directory (self.results_directory) def populate_from_directory (self, directory): thread = BuildManager.BuildManagerPopulaterThread (self, directory) thread.start() # Come up with the name for the next build ident by combining "build-" # with the date formatted as yyyymmdd and then an ordinal. We do this by # an optimistic algorithm incrementing the ordinal if we find that it # already exists. def get_next_build_ident (self): today = datetime.date.today () datestr = str (today.year) + str (today.month) + str (today.day) revision = 0 test_name = "build-%s-%d" % (datestr, revision) test_path = os.path.join (self.results_directory, test_name) while (os.path.exists (test_path)): revision += 1 test_name = "build-%s-%d" % (datestr, revision) test_path = os.path.join (self.results_directory, test_name) return test_name # Take a BuildConfiguration and then try and build it based on the # parameters of that configuration. S def do_build (self, conf): server = self.server # Work out the build directory. Note we actually create the # directories here since we need to write the ".conf" file. Otherwise # we could have relied on bitbake's builder thread to actually make # the directories as it proceeds with the build. ident = self.get_next_build_ident () build_directory = os.path.join (self.results_directory, ident) self.cur_build_directory = build_directory os.makedirs (build_directory) conffile = os.path.join (build_directory, ".conf") conf.write_to_file (conffile) # Add a row to the model representing this ongoing build. It's kinda a # fake entry. If this build completes or fails then this gets updated # with the real stuff like the historic builds date = long (time.time()) self.model.append (None, (ident, conf.image, conf.machine, conf.distro, None, date, BuildResult.STATE_ONGOING)) try: server.runCommand(["setVariable", "BUILD_IMAGES_FROM_FEEDS", 1]) server.runCommand(["setVariable", "MACHINE", conf.machine]) server.runCommand(["setVariable", "DISTRO", conf.distro]) server.runCommand(["setVariable", "PACKAGE_CLASSES", "package_ipk"]) server.runCommand(["setVariable", "BBFILES", \ """${OEROOT}/meta/packages/*/*.bb ${OEROOT}/meta-moblin/packages/*/*.bb"""]) server.runCommand(["setVariable", "TMPDIR", "${OEROOT}/build/tmp"]) server.runCommand(["setVariable", "IPK_FEED_URIS", \ " ".join(conf.get_repos())]) server.runCommand(["setVariable", "DEPLOY_DIR_IMAGE", build_directory]) server.runCommand(["buildTargets", [conf.image], "rootfs"]) except Exception as e: print(e) class BuildManagerTreeView (gtk.TreeView): """ The tree view for the build manager. This shows the historic builds and so forth. """ # We use this function to control what goes in the cell since we store # the date in the model as seconds since the epoch (for sorting) and so we # need to make it human readable. def date_format_custom_cell_data_func (self, col, cell, model, iter): date = model.get (iter, BuildManagerModel.COL_DATE)[0] datestr = time.strftime("%A %d %B %Y", time.localtime(date)) cell.set_property ("text", datestr) # This format function controls what goes in the cell. We use this to map # the integer state to a string and also to colourise the text def state_format_custom_cell_data_fun (self, col, cell, model, iter): state = model.get (iter, BuildManagerModel.COL_STATE)[0] if (state == BuildResult.STATE_ONGOING): cell.set_property ("text", "Active") cell.set_property ("foreground", "#000000") elif (state == BuildResult.STATE_FAILED): cell.set_property ("text", "Failed") cell.set_property ("foreground", "#ff0000") elif (state == BuildResult.STATE_COMPLETE): cell.set_property ("text", "Complete") cell.set_property ("foreground", "#00ff00") else: cell.set_property ("text", "") def __init__ (self): gtk.TreeView.__init__(self) # Misc descriptiony thing renderer = gtk.CellRendererText () col = gtk.TreeViewColumn (None, renderer, text=BuildManagerModel.COL_DESC) self.append_column (col) # Machine renderer = gtk.CellRendererText () col = gtk.TreeViewColumn ("Machine", renderer, text=BuildManagerModel.COL_MACHINE) self.append_column (col) # distro renderer = gtk.CellRendererText () col = gtk.TreeViewColumn ("Distribution", renderer, text=BuildManagerModel.COL_DISTRO) self.append_column (col) # date (using a custom function for formatting the cell contents it # takes epoch -> human readable string) renderer = gtk.CellRendererText () col = gtk.TreeViewColumn ("Date", renderer, text=BuildManagerModel.COL_DATE) self.append_column (col) col.set_cell_data_func (renderer, self.date_format_custom_cell_data_func) # For status. renderer = gtk.CellRendererText () col = gtk.TreeViewColumn ("Status", renderer, text = BuildManagerModel.COL_STATE) self.append_column (col) col.set_cell_data_func (renderer, self.state_format_custom_cell_data_fun)