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