1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# BitBake Graphical GTK based Dependency Explorer 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun# Copyright (C) 2007 Ross Burton 5*4882a593Smuzhiyun# Copyright (C) 2007 - 2008 Richard Purdie 6*4882a593Smuzhiyun# 7*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 8*4882a593Smuzhiyun# 9*4882a593Smuzhiyun 10*4882a593Smuzhiyunimport sys 11*4882a593Smuzhiyunimport traceback 12*4882a593Smuzhiyun 13*4882a593Smuzhiyuntry: 14*4882a593Smuzhiyun import gi 15*4882a593Smuzhiyun gi.require_version('Gtk', '3.0') 16*4882a593Smuzhiyun from gi.repository import Gtk, Gdk, GObject 17*4882a593Smuzhiyunexcept ValueError: 18*4882a593Smuzhiyun sys.exit("FATAL: Gtk version needs to be 3.0") 19*4882a593Smuzhiyunexcept ImportError: 20*4882a593Smuzhiyun sys.exit("FATAL: Gtk ui could not load the required gi python module") 21*4882a593Smuzhiyun 22*4882a593Smuzhiyunimport threading 23*4882a593Smuzhiyunfrom xmlrpc import client 24*4882a593Smuzhiyunimport bb 25*4882a593Smuzhiyunimport bb.event 26*4882a593Smuzhiyun 27*4882a593Smuzhiyun# Package Model 28*4882a593Smuzhiyun(COL_PKG_NAME) = (0) 29*4882a593Smuzhiyun 30*4882a593Smuzhiyun# Dependency Model 31*4882a593Smuzhiyun(TYPE_DEP, TYPE_RDEP) = (0, 1) 32*4882a593Smuzhiyun(COL_DEP_TYPE, COL_DEP_PARENT, COL_DEP_PACKAGE) = (0, 1, 2) 33*4882a593Smuzhiyun 34*4882a593Smuzhiyun 35*4882a593Smuzhiyunclass PackageDepView(Gtk.TreeView): 36*4882a593Smuzhiyun def __init__(self, model, dep_type, label): 37*4882a593Smuzhiyun Gtk.TreeView.__init__(self) 38*4882a593Smuzhiyun self.current = None 39*4882a593Smuzhiyun self.dep_type = dep_type 40*4882a593Smuzhiyun self.filter_model = model.filter_new() 41*4882a593Smuzhiyun self.filter_model.set_visible_func(self._filter, data=None) 42*4882a593Smuzhiyun self.set_model(self.filter_model) 43*4882a593Smuzhiyun self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PACKAGE)) 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun def _filter(self, model, iter, data): 46*4882a593Smuzhiyun this_type = model[iter][COL_DEP_TYPE] 47*4882a593Smuzhiyun package = model[iter][COL_DEP_PARENT] 48*4882a593Smuzhiyun if this_type != self.dep_type: return False 49*4882a593Smuzhiyun return package == self.current 50*4882a593Smuzhiyun 51*4882a593Smuzhiyun def set_current_package(self, package): 52*4882a593Smuzhiyun self.current = package 53*4882a593Smuzhiyun self.filter_model.refilter() 54*4882a593Smuzhiyun 55*4882a593Smuzhiyun 56*4882a593Smuzhiyunclass PackageReverseDepView(Gtk.TreeView): 57*4882a593Smuzhiyun def __init__(self, model, label): 58*4882a593Smuzhiyun Gtk.TreeView.__init__(self) 59*4882a593Smuzhiyun self.current = None 60*4882a593Smuzhiyun self.filter_model = model.filter_new() 61*4882a593Smuzhiyun self.filter_model.set_visible_func(self._filter) 62*4882a593Smuzhiyun # The introspected API was fixed but we can't rely on a pygobject that hides this. 63*4882a593Smuzhiyun # https://gitlab.gnome.org/GNOME/pygobject/-/commit/9cdbc56fbac4db2de78dc080934b8f0a7efc892a 64*4882a593Smuzhiyun if hasattr(Gtk.TreeModelSort, "new_with_model"): 65*4882a593Smuzhiyun self.sort_model = Gtk.TreeModelSort.new_with_model(self.filter_model) 66*4882a593Smuzhiyun else: 67*4882a593Smuzhiyun self.sort_model = self.filter_model.sort_new_with_model() 68*4882a593Smuzhiyun self.sort_model.set_sort_column_id(COL_DEP_PARENT, Gtk.SortType.ASCENDING) 69*4882a593Smuzhiyun self.set_model(self.sort_model) 70*4882a593Smuzhiyun self.append_column(Gtk.TreeViewColumn(label, Gtk.CellRendererText(), text=COL_DEP_PARENT)) 71*4882a593Smuzhiyun 72*4882a593Smuzhiyun def _filter(self, model, iter, data): 73*4882a593Smuzhiyun package = model[iter][COL_DEP_PACKAGE] 74*4882a593Smuzhiyun return package == self.current 75*4882a593Smuzhiyun 76*4882a593Smuzhiyun def set_current_package(self, package): 77*4882a593Smuzhiyun self.current = package 78*4882a593Smuzhiyun self.filter_model.refilter() 79*4882a593Smuzhiyun 80*4882a593Smuzhiyun 81*4882a593Smuzhiyunclass DepExplorer(Gtk.Window): 82*4882a593Smuzhiyun def __init__(self): 83*4882a593Smuzhiyun Gtk.Window.__init__(self) 84*4882a593Smuzhiyun self.set_title("Task Dependency Explorer") 85*4882a593Smuzhiyun self.set_default_size(500, 500) 86*4882a593Smuzhiyun self.connect("delete-event", Gtk.main_quit) 87*4882a593Smuzhiyun 88*4882a593Smuzhiyun # Create the data models 89*4882a593Smuzhiyun self.pkg_model = Gtk.ListStore(GObject.TYPE_STRING) 90*4882a593Smuzhiyun self.pkg_model.set_sort_column_id(COL_PKG_NAME, Gtk.SortType.ASCENDING) 91*4882a593Smuzhiyun self.depends_model = Gtk.ListStore(GObject.TYPE_INT, GObject.TYPE_STRING, GObject.TYPE_STRING) 92*4882a593Smuzhiyun self.depends_model.set_sort_column_id(COL_DEP_PACKAGE, Gtk.SortType.ASCENDING) 93*4882a593Smuzhiyun 94*4882a593Smuzhiyun pane = Gtk.HPaned() 95*4882a593Smuzhiyun pane.set_position(250) 96*4882a593Smuzhiyun self.add(pane) 97*4882a593Smuzhiyun 98*4882a593Smuzhiyun # The master list of packages 99*4882a593Smuzhiyun scrolled = Gtk.ScrolledWindow() 100*4882a593Smuzhiyun scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 101*4882a593Smuzhiyun scrolled.set_shadow_type(Gtk.ShadowType.IN) 102*4882a593Smuzhiyun 103*4882a593Smuzhiyun self.pkg_treeview = Gtk.TreeView(self.pkg_model) 104*4882a593Smuzhiyun self.pkg_treeview.get_selection().connect("changed", self.on_cursor_changed) 105*4882a593Smuzhiyun column = Gtk.TreeViewColumn("Package", Gtk.CellRendererText(), text=COL_PKG_NAME) 106*4882a593Smuzhiyun self.pkg_treeview.append_column(column) 107*4882a593Smuzhiyun scrolled.add(self.pkg_treeview) 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun self.search_entry = Gtk.SearchEntry.new() 110*4882a593Smuzhiyun self.pkg_treeview.set_search_entry(self.search_entry) 111*4882a593Smuzhiyun 112*4882a593Smuzhiyun left_panel = Gtk.VPaned() 113*4882a593Smuzhiyun left_panel.add(self.search_entry) 114*4882a593Smuzhiyun left_panel.add(scrolled) 115*4882a593Smuzhiyun pane.add1(left_panel) 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun box = Gtk.VBox(homogeneous=True, spacing=4) 118*4882a593Smuzhiyun 119*4882a593Smuzhiyun # Task Depends 120*4882a593Smuzhiyun scrolled = Gtk.ScrolledWindow() 121*4882a593Smuzhiyun scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 122*4882a593Smuzhiyun scrolled.set_shadow_type(Gtk.ShadowType.IN) 123*4882a593Smuzhiyun self.dep_treeview = PackageDepView(self.depends_model, TYPE_DEP, "Dependencies") 124*4882a593Smuzhiyun self.dep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PACKAGE) 125*4882a593Smuzhiyun scrolled.add(self.dep_treeview) 126*4882a593Smuzhiyun box.add(scrolled) 127*4882a593Smuzhiyun pane.add2(box) 128*4882a593Smuzhiyun 129*4882a593Smuzhiyun # Reverse Task Depends 130*4882a593Smuzhiyun scrolled = Gtk.ScrolledWindow() 131*4882a593Smuzhiyun scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 132*4882a593Smuzhiyun scrolled.set_shadow_type(Gtk.ShadowType.IN) 133*4882a593Smuzhiyun self.revdep_treeview = PackageReverseDepView(self.depends_model, "Dependent Tasks") 134*4882a593Smuzhiyun self.revdep_treeview.connect("row-activated", self.on_package_activated, COL_DEP_PARENT) 135*4882a593Smuzhiyun scrolled.add(self.revdep_treeview) 136*4882a593Smuzhiyun box.add(scrolled) 137*4882a593Smuzhiyun pane.add2(box) 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun self.show_all() 140*4882a593Smuzhiyun self.search_entry.grab_focus() 141*4882a593Smuzhiyun 142*4882a593Smuzhiyun def on_package_activated(self, treeview, path, column, data_col): 143*4882a593Smuzhiyun model = treeview.get_model() 144*4882a593Smuzhiyun package = model.get_value(model.get_iter(path), data_col) 145*4882a593Smuzhiyun 146*4882a593Smuzhiyun pkg_path = [] 147*4882a593Smuzhiyun def finder(model, path, iter, needle): 148*4882a593Smuzhiyun package = model.get_value(iter, COL_PKG_NAME) 149*4882a593Smuzhiyun if package == needle: 150*4882a593Smuzhiyun pkg_path.append(path) 151*4882a593Smuzhiyun return True 152*4882a593Smuzhiyun else: 153*4882a593Smuzhiyun return False 154*4882a593Smuzhiyun self.pkg_model.foreach(finder, package) 155*4882a593Smuzhiyun if pkg_path: 156*4882a593Smuzhiyun self.pkg_treeview.get_selection().select_path(pkg_path[0]) 157*4882a593Smuzhiyun self.pkg_treeview.scroll_to_cell(pkg_path[0]) 158*4882a593Smuzhiyun 159*4882a593Smuzhiyun def on_cursor_changed(self, selection): 160*4882a593Smuzhiyun (model, it) = selection.get_selected() 161*4882a593Smuzhiyun if it is None: 162*4882a593Smuzhiyun current_package = None 163*4882a593Smuzhiyun else: 164*4882a593Smuzhiyun current_package = model.get_value(it, COL_PKG_NAME) 165*4882a593Smuzhiyun self.dep_treeview.set_current_package(current_package) 166*4882a593Smuzhiyun self.revdep_treeview.set_current_package(current_package) 167*4882a593Smuzhiyun 168*4882a593Smuzhiyun 169*4882a593Smuzhiyun def parse(self, depgraph): 170*4882a593Smuzhiyun for task in depgraph["tdepends"]: 171*4882a593Smuzhiyun self.pkg_model.insert(0, (task,)) 172*4882a593Smuzhiyun for depend in depgraph["tdepends"][task]: 173*4882a593Smuzhiyun self.depends_model.insert (0, (TYPE_DEP, task, depend)) 174*4882a593Smuzhiyun 175*4882a593Smuzhiyun 176*4882a593Smuzhiyunclass gtkthread(threading.Thread): 177*4882a593Smuzhiyun quit = threading.Event() 178*4882a593Smuzhiyun def __init__(self, shutdown): 179*4882a593Smuzhiyun threading.Thread.__init__(self) 180*4882a593Smuzhiyun self.setDaemon(True) 181*4882a593Smuzhiyun self.shutdown = shutdown 182*4882a593Smuzhiyun if not Gtk.init_check()[0]: 183*4882a593Smuzhiyun sys.stderr.write("Gtk+ init failed. Make sure DISPLAY variable is set.\n") 184*4882a593Smuzhiyun gtkthread.quit.set() 185*4882a593Smuzhiyun 186*4882a593Smuzhiyun def run(self): 187*4882a593Smuzhiyun GObject.threads_init() 188*4882a593Smuzhiyun Gdk.threads_init() 189*4882a593Smuzhiyun Gtk.main() 190*4882a593Smuzhiyun gtkthread.quit.set() 191*4882a593Smuzhiyun 192*4882a593Smuzhiyun 193*4882a593Smuzhiyundef main(server, eventHandler, params): 194*4882a593Smuzhiyun shutdown = 0 195*4882a593Smuzhiyun 196*4882a593Smuzhiyun gtkgui = gtkthread(shutdown) 197*4882a593Smuzhiyun gtkgui.start() 198*4882a593Smuzhiyun 199*4882a593Smuzhiyun try: 200*4882a593Smuzhiyun params.updateToServer(server, os.environ.copy()) 201*4882a593Smuzhiyun params.updateFromServer(server) 202*4882a593Smuzhiyun cmdline = params.parseActions() 203*4882a593Smuzhiyun if not cmdline: 204*4882a593Smuzhiyun print("Nothing to do. Use 'bitbake world' to build everything, or run 'bitbake --help' for usage information.") 205*4882a593Smuzhiyun return 1 206*4882a593Smuzhiyun if 'msg' in cmdline and cmdline['msg']: 207*4882a593Smuzhiyun print(cmdline['msg']) 208*4882a593Smuzhiyun return 1 209*4882a593Smuzhiyun cmdline = cmdline['action'] 210*4882a593Smuzhiyun if not cmdline or cmdline[0] != "generateDotGraph": 211*4882a593Smuzhiyun print("This UI requires the -g option") 212*4882a593Smuzhiyun return 1 213*4882a593Smuzhiyun ret, error = server.runCommand(["generateDepTreeEvent", cmdline[1], cmdline[2]]) 214*4882a593Smuzhiyun if error: 215*4882a593Smuzhiyun print("Error running command '%s': %s" % (cmdline, error)) 216*4882a593Smuzhiyun return 1 217*4882a593Smuzhiyun elif not ret: 218*4882a593Smuzhiyun print("Error running command '%s': returned %s" % (cmdline, ret)) 219*4882a593Smuzhiyun return 1 220*4882a593Smuzhiyun except client.Fault as x: 221*4882a593Smuzhiyun print("XMLRPC Fault getting commandline:\n %s" % x) 222*4882a593Smuzhiyun return 223*4882a593Smuzhiyun except Exception as e: 224*4882a593Smuzhiyun print("Exception in startup:\n %s" % traceback.format_exc()) 225*4882a593Smuzhiyun return 226*4882a593Smuzhiyun 227*4882a593Smuzhiyun if gtkthread.quit.isSet(): 228*4882a593Smuzhiyun return 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun Gdk.threads_enter() 231*4882a593Smuzhiyun dep = DepExplorer() 232*4882a593Smuzhiyun bardialog = Gtk.Dialog(parent=dep, 233*4882a593Smuzhiyun flags=Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT) 234*4882a593Smuzhiyun bardialog.set_default_size(400, 50) 235*4882a593Smuzhiyun box = bardialog.get_content_area() 236*4882a593Smuzhiyun pbar = Gtk.ProgressBar() 237*4882a593Smuzhiyun box.pack_start(pbar, True, True, 0) 238*4882a593Smuzhiyun bardialog.show_all() 239*4882a593Smuzhiyun bardialog.connect("delete-event", Gtk.main_quit) 240*4882a593Smuzhiyun Gdk.threads_leave() 241*4882a593Smuzhiyun 242*4882a593Smuzhiyun progress_total = 0 243*4882a593Smuzhiyun while True: 244*4882a593Smuzhiyun try: 245*4882a593Smuzhiyun event = eventHandler.waitEvent(0.25) 246*4882a593Smuzhiyun if gtkthread.quit.isSet(): 247*4882a593Smuzhiyun _, error = server.runCommand(["stateForceShutdown"]) 248*4882a593Smuzhiyun if error: 249*4882a593Smuzhiyun print('Unable to cleanly stop: %s' % error) 250*4882a593Smuzhiyun break 251*4882a593Smuzhiyun 252*4882a593Smuzhiyun if event is None: 253*4882a593Smuzhiyun continue 254*4882a593Smuzhiyun 255*4882a593Smuzhiyun if isinstance(event, bb.event.CacheLoadStarted): 256*4882a593Smuzhiyun progress_total = event.total 257*4882a593Smuzhiyun Gdk.threads_enter() 258*4882a593Smuzhiyun bardialog.set_title("Loading Cache") 259*4882a593Smuzhiyun pbar.set_fraction(0.0) 260*4882a593Smuzhiyun Gdk.threads_leave() 261*4882a593Smuzhiyun 262*4882a593Smuzhiyun if isinstance(event, bb.event.CacheLoadProgress): 263*4882a593Smuzhiyun x = event.current 264*4882a593Smuzhiyun Gdk.threads_enter() 265*4882a593Smuzhiyun pbar.set_fraction(x * 1.0 / progress_total) 266*4882a593Smuzhiyun Gdk.threads_leave() 267*4882a593Smuzhiyun continue 268*4882a593Smuzhiyun 269*4882a593Smuzhiyun if isinstance(event, bb.event.CacheLoadCompleted): 270*4882a593Smuzhiyun continue 271*4882a593Smuzhiyun 272*4882a593Smuzhiyun if isinstance(event, bb.event.ParseStarted): 273*4882a593Smuzhiyun progress_total = event.total 274*4882a593Smuzhiyun if progress_total == 0: 275*4882a593Smuzhiyun continue 276*4882a593Smuzhiyun Gdk.threads_enter() 277*4882a593Smuzhiyun pbar.set_fraction(0.0) 278*4882a593Smuzhiyun bardialog.set_title("Processing recipes") 279*4882a593Smuzhiyun Gdk.threads_leave() 280*4882a593Smuzhiyun 281*4882a593Smuzhiyun if isinstance(event, bb.event.ParseProgress): 282*4882a593Smuzhiyun x = event.current 283*4882a593Smuzhiyun Gdk.threads_enter() 284*4882a593Smuzhiyun pbar.set_fraction(x * 1.0 / progress_total) 285*4882a593Smuzhiyun Gdk.threads_leave() 286*4882a593Smuzhiyun continue 287*4882a593Smuzhiyun 288*4882a593Smuzhiyun if isinstance(event, bb.event.ParseCompleted): 289*4882a593Smuzhiyun Gdk.threads_enter() 290*4882a593Smuzhiyun bardialog.set_title("Generating dependency tree") 291*4882a593Smuzhiyun Gdk.threads_leave() 292*4882a593Smuzhiyun continue 293*4882a593Smuzhiyun 294*4882a593Smuzhiyun if isinstance(event, bb.event.DepTreeGenerated): 295*4882a593Smuzhiyun Gdk.threads_enter() 296*4882a593Smuzhiyun bardialog.hide() 297*4882a593Smuzhiyun dep.parse(event._depgraph) 298*4882a593Smuzhiyun Gdk.threads_leave() 299*4882a593Smuzhiyun 300*4882a593Smuzhiyun if isinstance(event, bb.command.CommandCompleted): 301*4882a593Smuzhiyun continue 302*4882a593Smuzhiyun 303*4882a593Smuzhiyun if isinstance(event, bb.event.NoProvider): 304*4882a593Smuzhiyun print(str(event)) 305*4882a593Smuzhiyun 306*4882a593Smuzhiyun _, error = server.runCommand(["stateShutdown"]) 307*4882a593Smuzhiyun if error: 308*4882a593Smuzhiyun print('Unable to cleanly shutdown: %s' % error) 309*4882a593Smuzhiyun break 310*4882a593Smuzhiyun 311*4882a593Smuzhiyun if isinstance(event, bb.command.CommandFailed): 312*4882a593Smuzhiyun print(str(event)) 313*4882a593Smuzhiyun return event.exitcode 314*4882a593Smuzhiyun 315*4882a593Smuzhiyun if isinstance(event, bb.command.CommandExit): 316*4882a593Smuzhiyun return event.exitcode 317*4882a593Smuzhiyun 318*4882a593Smuzhiyun if isinstance(event, bb.cooker.CookerExit): 319*4882a593Smuzhiyun break 320*4882a593Smuzhiyun 321*4882a593Smuzhiyun continue 322*4882a593Smuzhiyun except EnvironmentError as ioerror: 323*4882a593Smuzhiyun # ignore interrupted io 324*4882a593Smuzhiyun if ioerror.args[0] == 4: 325*4882a593Smuzhiyun pass 326*4882a593Smuzhiyun except KeyboardInterrupt: 327*4882a593Smuzhiyun if shutdown == 2: 328*4882a593Smuzhiyun print("\nThird Keyboard Interrupt, exit.\n") 329*4882a593Smuzhiyun break 330*4882a593Smuzhiyun if shutdown == 1: 331*4882a593Smuzhiyun print("\nSecond Keyboard Interrupt, stopping...\n") 332*4882a593Smuzhiyun _, error = server.runCommand(["stateForceShutdown"]) 333*4882a593Smuzhiyun if error: 334*4882a593Smuzhiyun print('Unable to cleanly stop: %s' % error) 335*4882a593Smuzhiyun if shutdown == 0: 336*4882a593Smuzhiyun print("\nKeyboard Interrupt, closing down...\n") 337*4882a593Smuzhiyun _, error = server.runCommand(["stateShutdown"]) 338*4882a593Smuzhiyun if error: 339*4882a593Smuzhiyun print('Unable to cleanly shutdown: %s' % error) 340*4882a593Smuzhiyun shutdown = shutdown + 1 341*4882a593Smuzhiyun pass 342