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